dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.88k stars 784 forks source link

Using multiple operators in INumber-constrained generic functions leads to compiler error #17062

Open En3Tho opened 5 months ago

En3Tho commented 5 months ago

Please provide a succinct description of the issue.

// ok
let numericOpsPlus<'T when INumber<'T>>(num: 'T) =
    num + num

// FS0670 - code is not sufficiently generic
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    num + num + num

// ok again
let inline numericOpsPlusPlusInlined<'T when INumber<'T>>(num: 'T) =
    num + num + num
vzarytovskii commented 5 months ago

Note: Works fine when chaining op_Addition calls.

brianrourkeboll commented 5 months ago

For reference — these tests should be extended to cover this:

https://github.com/dotnet/fsharp/blob/273cea532b81b33596d9635c6d8c6fb0a610093d/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/testFiles/UseSRTPFromIWSAMGenericCode.fs#L1-L33

brianrourkeboll commented 5 months ago

Note: Works fine when chaining op_Addition calls.

Yes, as in

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.(+) (num, num')
    |> fun num' -> 'T.(+) (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.op_Addition (num, num')
    |> fun num' -> 'T.op_Addition (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    let (+) x y = 'T.(+) (x, y)
    num + num + num
vzarytovskii commented 5 months ago

Note: Works fine when chaining op_Addition calls.

Yes, as in

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.(+) (num, num')
    |> fun num' -> 'T.(+) (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.op_Addition (num, num')
    |> fun num' -> 'T.op_Addition (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    let (+) x y = 'T.(+) (x, y)
    num + num + num

Yes, also:

open System.Numerics
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    ((num + num) : 'T) + num

and

open System.Numerics
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    'T.op_Addition('T.op_Addition(num, num), num)
vzarytovskii commented 5 months ago

We suffer flexibility of the (+) here. I am not fully sure we can "fix" it here without seriously breaking something. I wonder if it's the same with some other operators (maybe not that common in F#).

brianrourkeboll commented 5 months ago

Yes, also:

open System.Numerics
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    ((num + num) : 'T) + num

This might show where the problem is: something is probably getting confused when looking at the multiple type parameters in IAdditionOperators<'TSelf, 'TOther, 'TResult> and trying to unify the 'TResult of the first application with the 'TOther of the second.

vzarytovskii commented 5 months ago

Yes, also:


open System.Numerics

let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =

    ((num + num) : 'T) + num

This might show where the problem is: something is probably getting confused when looking at the multiple type parameters in IAdditionOperators<'TSelf, 'TOther, 'TResult> and trying to unify the 'TResult of the first application with the 'TOther of the second.

It's probably only in conjunction with + (might be others too).

vzarytovskii commented 5 months ago

IAdditionOperators<'TSelf, 'TOther, 'TResult> and trying to unify the 'TResult of the first application with the 'TOther of the second.

@brianrourkeboll regarding the above, since it's constrained via INumber, I would expect it to have all typars to be unified.

brianrourkeboll commented 5 months ago

It's probably only in conjunction with + (might be others too).

It does for the other System.Numerics operators (-, *, %, unary + and -, etc.).

Interestingly, this does not trigger the problem:

open System.Numerics

#nowarn "3535"

type IFace<'a, 'b, 'c> =
    static abstract (>*..*<) : 'a * 'b -> 'c

let customOps<'T when IFace<'T, 'T, 'T>> (num: 'T) =
    num >*..*< num >*..*< num

type T =
    | T of int
    interface IFace<T, T, T> with
        static member (>*..*<) (T a, T b) = T (a + b)

customOps (T 1)
vzarytovskii commented 5 months ago

It's probably only in conjunction with + (might be others too).

It does for the other System.Numerics operators (-, *, %, unary + and -, etc.).

Interestingly, this does not trigger the problem:


open System.Numerics

#nowarn "3535"

type IFace<'a, 'b, 'c> =

    static abstract (>*..*<) : 'a * 'b -> 'c

let customOps<'T when IFace<'T, 'T, 'T>> (num: 'T) =

    num >*..*< num >*..*< num

type T =

    | T of int

    interface IFace<T, T, T> with

        static member (>*..*<) (T a, T b) = T (a + b)

customOps (T 1)

Yeah, many of the "standard" operators have somewhat special treatment.

En3Tho commented 5 months ago

By the way, this is how I found this bug: https://github.com/m4rs-mt/ILGPU/issues/463#issuecomment-2061598101

I've been playing with F# and ILGpu and so far it feels quite nice.

vzarytovskii commented 5 months ago

When using inline, code does compile to

public static T numericOpsPlus<T>(T num) where T : INumber<T>
    {
        T val = num + num;
        return val + num;
    }