fsprojects / FSharp.TypeProviders.SDK

The SDK for creating F# type providers
https://fsprojects.github.io/FSharp.TypeProviders.SDK/
MIT License
298 stars 94 forks source link

Specified method is not supported - Operators.op_Subtraction[T1,T2,T3](T1 x, T2 y) #206

Closed sergey-tihon closed 4 years ago

sergey-tihon commented 6 years ago

Description

for loop used in quotation compiles but fails at runtime with exception

System.NotSupportedException: Specified method is not supported.
at Microsoft.FSharp.Core.Operators.op_Subtraction[T1,T2,T3](T1 x, T2 y)
at Lambda30cb63ff-f943-4fb8-8bb4-125c35c8a163.Invoke(Unit )
at Microsoft.FSharp.Control.AsyncBuilderImpl.callA@522.Invoke(AsyncParams`1 args)

the quotation is

<@
       ...
       for (name, value) in %heads do msg.Headers.Add(name, value)
       msg 
@>

Actual behavior

Error message on Mono is even less readable

System.NotSupportedException: Specified method is not supported.
  at Lambdaae052712-1c22-4799-9b8d-5860aa66eb67.Invoke (Microsoft.FSharp.Core.Unit ) [0x00175] in <5a91431651cfe594a74503831643915a>:0 
  at Microsoft.FSharp.Control.AsyncBuilderImpl+callA@522[b,a].Invoke (Microsoft.FSharp.Control.AsyncParams`1[T] args) [0x00051] in <5a7d678a904cf4daa74503838a677d5a>:0 

Known workarounds

Rewrite loop using Seq.iter. Following code works

<@
     %heads
     |> Seq.iter (fun (name, value) ->
          msg.Headers.Add(name, value)
      )
     msg 
@>

Related information

More details are here -https://github.com/fsprojects/SwaggerProvider/pull/105#issuecomment-368220077

Generated code decompiled to C#

Tuple<string, string>[] array = (!Operators.Not(flag) || 1 == 0) ? ((ProvidedSwaggerBaseType)client).Headers : ArrayModule.Append(new Tuple<string, string>[1]
        {
            new Tuple<string, string>("Content-Type", "application/json")
        }, ((ProvidedSwaggerBaseType)client).Headers);
int num = 0;
while (true)
{
    if (num > ArrayModule.Length(array) - 1)
    {
        break;
    }
    Tuple<string, string> array2 = LanguagePrimitives.IntrinsicFunctions.GetArray(array, num);
    string item = array2.Item2;
    string item2 = array2.Item1;
    httpRequestMessage.Headers.Add(item2, item);
    num++;
}

IL code of the loop

IL_0167: stloc.s 12
IL_0169: ldc.i4.0
IL_016a: stloc.s 15
// loop start (head: IL_016c)
    IL_016c: ldloc.s 15
    IL_016e: ldloc.s 12
    IL_0170: call int32 [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::Length<class [mscorlib]System.Tuple`2<string, string>>(!!0[])
    IL_0175: ldc.i4.1
    IL_0176: call !!2 [FSharp.Core]Microsoft.FSharp.Core.Operators::op_Subtraction<int32, int32, int32>(!!0, !!1)
    IL_017b: bgt.s IL_01b2

    IL_017d: ldloc.s 12
    IL_017f: ldloc.s 15
    IL_0181: call !!0 [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::GetArray<class [mscorlib]System.Tuple`2<string, string>>(!!0[], int32)
    IL_0186: stloc.s 16
    IL_0188: ldloc.s 16
    IL_018a: call instance !1 class [mscorlib]System.Tuple`2<string, string>::get_Item2()
    IL_018f: stloc.s 17
    IL_0191: ldloc.s 16
    IL_0193: call instance !0 class [mscorlib]System.Tuple`2<string, string>::get_Item1()
    IL_0198: stloc.s 18
    IL_019a: ldloc.s 11
    IL_019c: call instance class [System.Net.Http]System.Net.Http.Headers.HttpRequestHeaders [System.Net.Http]System.Net.Http.HttpRequestMessage::get_Headers()
    IL_01a1: ldloc.s 18
    IL_01a3: ldloc.s 17
    IL_01a5: call instance void [System.Net.Http]System.Net.Http.Headers.HttpHeaders::Add(string, string)
    IL_01aa: ldloc.s 15
    IL_01ac: ldc.i4.1
    IL_01ad: add
    IL_01ae: stloc.s 15
    IL_01b0: br.s IL_016c
// end loop
dsyme commented 6 years ago

Looks like any use of for loops is failing because of #241

sergey-tihon commented 5 years ago

I still can reproduce it when I do

let toStr = 
    ProvidedMethod("ToString", [], typeof<string>, isStatic = false, 
        invokeCode = fun args ->
            let this = args.[0]
            let (pNames, pValues) =
                Array.ofList members
                |> Array.map (fun (pField, pProp) ->
                    let pValObj = Expr.FieldGet(this, pField)
                    pProp.Name, Expr.Coerce(pValObj, typeof<obj>)
                   )
                |> Array.unzip
            let pValuesArr = Expr.NewArray(typeof<obj>, List.ofArray pValues)
            <@@
                let values = (%%pValuesArr : array<obj>)
                let rec formatValue (v:obj) =
                    if isNull v then "null"
                    else
                        let vTy = v.GetType()
                        if vTy = typeof<string>
                        then String.Format("\"{0}\"",v)
                        elif vTy.IsArray
                        then
                            let elements =
                                //(v :?> seq<_>) |> Seq.map formatValue
                                // !!! READ HERE
                                [| for x in (v :?> Collections.IEnumerable) do
                                     yield formatValue x 
                                |]
                            String.Format("[{0}]", String.Join("; ", elements))
                        else v.ToString()

                let strs = values |> Array.mapi (fun i v ->
                    String.Format("{0}={1}",pNames.[i], formatValue v))
                String.Format("{{{0}}}", String.Join("; ",strs))
            @@>)
toStr.SetMethodAttrs(MethodAttributes.Public ||| MethodAttributes.Virtual)

let objToStr = (typeof<obj>).GetMethod("ToString",[||])
ty.DefineMethodOverride(toStr, objToStr)
ty.AddMember <| toStr

This code fails during dotnet build (of proj that uses TP) with error

error FS3033: The type provider 'SwaggerProvider.SwaggerTypeProvider' reported an error: The method or operation is not implemented.

but when I replace

 let elements =
    // !!! READ HERE
    [| for x in (v :?> Collections.IEnumerable) do
         yield formatValue x 
    |]

by

 let elements =
    (v :?> seq<_>) |> Seq.map formatValue

it compile without errors

P.S. IIRC it worked before ...

kevmal commented 5 years ago

A simple <@ a - b @> doesn't work. Repro https://github.com/kevmal/tpsub

Method: https://github.com/kevmal/tpsub/blob/4f1cb43d318925787403612c1f411a38a0d6a639/src/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fs#L88-L89

Test case: https://github.com/kevmal/tpsub/blob/4f1cb43d318925787403612c1f411a38a0d6a639/tests/LemonadeProvider.Tests/LemonadeProvider.Tests.fs#L38-L41

Test fails with the same Message: System.NotSupportedException : Specified method is not supported.

kevmal commented 5 years ago

My quick fix ended up being adding to CodeGenerator

            | Call (None,meth,[a1;a2]) when meth.DeclaringType.FullName = "Microsoft.FSharp.Core.Operators" ->
                let emit op = 
                    let t1 = a1.Type
                    emitExpr ExpectedStackState.Value a1
                    emitExpr ExpectedStackState.Value a2
                    let dec = (convTypeToTgt typeof<decimal>)
                    if t1 = dec then
                        let meth = dec.GetMethod meth.Name
                        ilg.Emit(I_call(Normalcall, transMeth meth, None))
                    else
                        ilg.Emit(op)
                        emitConvIfNecessary t1
                    popIfEmptyExpected expectedState
                match meth.Name with 
                | "op_Subtraction" -> emit (I_sub)
                | "op_Division" -> emit (I_div)
                | "op_Modulus" -> emit (I_rem)
                | _ -> emitCallExpr None meth [a1;a2]
            | Call (objOpt,meth,args) -> emitCallExpr objOpt meth args

where emitCallExpr was just the former Call branch.

Formerly ProvidedTypes.fs had SpecificCall branches like

        | SpecificCall <@ (-) @>(None, [t1; t2; _], [a1; a2]) ->
            assert(t1 = t2)
            emitExpr ExpectedStackState.Value a1
            emitExpr ExpectedStackState.Value a2
            if t1 = typeof<decimal> then
                ilg.Emit(OpCodes.Call, typeof<decimal>.GetMethod "op_Subtraction")
            else
                ilg.Emit(OpCodes.Sub)
                emitConvIfNecessary t1

            popIfEmptyExpected expectedState

Which are currently removed, this form would no longer work since it wont match "target" methods. Which raises the question if the SpecificCall branches that are there (for GetArray) are ever hit?

kevmal commented 5 years ago

@sergey-tihon Try this again if you get a chance.

IL_0176: call !!2 [FSharp.Core]Microsoft.FSharp.Core.Operators::op_Subtraction

should be replaced with the appropriate IL.

sergey-tihon commented 4 years ago

Let's count it as solved for now.