Skip to content

Allow inline functions to call other inline functions with a lower accessibility level. #8348

@teo-tsirpanis

Description

@teo-tsirpanis

Is your feature request related to a problem? Please describe.

Let's say we have a public function foo which uses SRTPs so must be inline. This function has to call a trivial utility function bar that is used from several different parts of the module and does not need to be known outside of the module. Because foo is inline and public, bar has to be public as well, exposing an insignificant implementation detail.

Describe the solution you'd like

If however, bar was private and inline, it could be inlined everywhere foo was used, in a transparent way that does not even imply bar's existence to code outside the module. That's the point of inline functions whatsoever.

Inline functions cannot access constructs with a lower accessibility level than itself, because it would expose hidden code to outsiders. Of course the latter inline function (bar in our case) has to follow that rule despite its accessibility level allowing it. I acknowledge it is kind of counterintuitive.

As for how to implement it, my idea was to keep the reference to toCharacters until the real inlining takes place (when genericUnsigned will be called from a non-inline function). When that happens, the inline functions will have to be recursively expanded.

Describe alternatives you've considered

I can either make bar public but clutter my public API, or manually inline bar's implementation every time it would have been called, but duplicate my code.

Additional context

I am writing a generic high-performance function to parse integers from ReadOnlySpans of characters. In .NET Standard 2.1, I can directly convert the span to an integer without wasting an allocation by converting it to a string first. I abstracted this behavior with a type alias that is a string in all platforms except .NET Standard 2.1 and a function which converts the string to a span in all platforms except .NET Standard 2.1.

type Characters =
    // Soon...
    #if NETSTANDARD_2_1
        ReadOnlySpan<char>
    #else
        string
    #endif

let toCharacters (x: ReadOnlySpan<char>): Characters =
    #if NETSTANDARD_2_1
        x
    #else
        x.ToString()
    #endif

The following piece of code is used thrice: twice for all types of (un)signed integers, and once for all types of floating-point numbers.

let inline genericUnsigned< ^TInt when ^TInt: (static member Parse: Characters * NumberStyles * IFormatProvider -> ^TInt)> name =
    terminal name
        (T (fun _ x ->
            (^TInt: (static member Parse: Characters * NumberStyles * IFormatProvider -> ^TInt)
                (toCharacters x, NumberStyles.Integer, NumberFormatInfo.InvariantInfo))))
        unsignedRegex

I would like to make the toCharacters function private for obvious reasons.
Update: As I was writing this issue, I realised that there is a similar problem with Characters. Characters is a type alias of a public type, not a new type, so no code will be exposed by inlining the type a bit earlier as well.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions