(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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With