Curious errors when combining inline with explicit

2019-06-20 04:07发布

问题:

(Updated: I have added a repro example)

With code looking like the following:

type Lib =
    static member inline tryMe (a: ^a) = 
        let name = (^a: (static member name: string) ())
        name

type Test =
    struct 
        val Value: string
        new v = {Value = v}
    end
    static member inline name with get() = "HiThere"
    static member works(a:Test) = Lib.tryMe a

This will "just work" and compile. However, if you extend it a little bit, for instance like as follows:

/// Does a bounds check and raises an error if bounds check is not met
let inline checkBounds f (g: 'b -> ^c) (tp: ^a) = 
    let convertFrom = (^a: (static member name: string) ())
    let convertTo = (^c: (static member name : string) ())
    let value =  (^a: (member Value: 'b) tp)
    if f value then
        g value
    else 
        failwithf "Cannot convert from %s to %s." convertFrom convertTo


type ConverterA =
    struct 
        val Value: sbyte
        new v = { Value = v }
    end

    static member inline name with get() = "converter-a"
    static member inline convert (x: ConverterA) : ConverterB = 
        checkBounds ((>=) 0y) (byte >> ConverterB) x

and ConverterB =
    struct 
        val Value: byte
        new v = { Value = v }
    end
    static member inline name with get() = "converter-b"

It will raise a whole bunch of spurious FSharp compiler errors.

error FS1114: The value 'Foo.Bar.name' was marked inline but was not bound in the optimization environment

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

warning FS1116: A value marked as 'inline' has an unexpected value

error FS1118: Failed to inline the value 'name' marked 'inline', perhaps because a recursive value was marked 'inline'

I haven't seen this happening with other inline functions. I am not sure what happens here. If I change just a little bit, for instance remove the convertTo line and its dependencies, it compiles fine.

The errors also don't appear when running the code in FSI, even with FSI set up with --optimize.

I can work around it by removing inline. For fields of this kind it doesn't matter much anyway, the JIT will inline them, even if F# hasn't.

Is this a compiler bug? Or is there an error in my code, or some restriction on explicit member constraints I haven't been aware of?

回答1:

You need to reorder so that the functions you use are known at the point where you use them, otherwise it seems that the F# compiler doesn't know what to inline. As you said in the comments below this answer this is a bug if you ask me.

/// Does a bounds check and raises an error if bounds check is not met
let inline checkBounds f (g: 'b -> ^c) (tp: ^a) = 
    let convertFrom = (^a: (static member name: string) ())
    let convertTo = (^c: (static member name : string) ())
    let value =  (^a: (member Value: 'b) tp)
    if f value then
        g value
    else 
        failwithf "Cannot convert from %s to %s." convertFrom convertTo

type ConverterB =
    struct 
        val Value: byte
        new v = { Value = v }
    end
    static member inline name with get() = "converter-b"

and ConverterA =
    struct 
        val Value: sbyte
        new v = { Value = v }
    end

    static member inline name with get() = "converter-a"
    static member inline convert (x: ConverterA) : ConverterB = 
        checkBounds ((>=) 0y) (byte >> ConverterB) x