Search code examples
reflectionf#type-providersdiscriminated-union

F# Type Provider Referencing Custom Types


I'm constructing a simple type provider, but I seem to be running into problems when referencing types I created. For instance, given

namespace Adder

type Summation = Summation of int

module QuickAdd = 
  let add x y = x + y |> Summation

I want to make the following test case pass:

module Adder.Tests

open Adder
open NUnit.Framework

type Simple = QuickAddProvider<1, 2>

[<Test>]
let ``Simple sample is 3`` () =
  let foo = Simple()
  Assert.AreEqual(foo.Sample, Summation 3)

With the following type provider:

namespace Adder

open Microsoft.FSharp.Core.CompilerServices
open ProviderImplementation.ProvidedTypes
open System.Reflection

[<TypeProvider>]
type public QuickAddProvider (config : TypeProviderConfig) as this =
  inherit TypeProviderForNamespaces ()

  let ns = "Adder"
  let asm = Assembly.GetExecutingAssembly()
  let paraProvTy = ProvidedTypeDefinition(asm, ns, "QuickAddProvider", Some typeof<obj>)

  let buildTypes (typeName:string) (args:obj[]) =
    let num1 = args.[0] :?> int
    let num2 = args.[1] :?> int
    let tpType = ProvidedTypeDefinition(asm, ns, typeName, Some typeof<obj>)
    let result = QuickAdd.add num1 num2
    let orig = ProvidedProperty("Sample", typeof<Summation>, GetterCode = (fun args -> <@@ result @@>))
    tpType.AddMember(orig)
    tpType.AddMember(ProvidedConstructor([], InvokeCode = (fun args -> <@@ () @@>)))
    tpType

  let parameters = 
    [ProvidedStaticParameter("Num1", typeof<int>)
     ProvidedStaticParameter("Num2", typeof<int>)]

  do paraProvTy.DefineStaticParameters(parameters, buildTypes)
  do this.AddNamespace(ns, [paraProvTy])

[<TypeProviderAssembly>]
do()

I run into unexpected errors in the test file:

The type provider 'Adder.QuickAddProvider' reported an error in the context of provided type 'Adder.QuickAddProvider,Num1="1",Num2="2"', member 'get_Sample'. The error: Unsupported constant type 'Adder.Summation'

With the following errors in the generated file:

The type "Summation" is not defined
The namespace or module "Adder" is not defined

The test case compiles and passes when replacing the Summation type with int, so I know my type provider isn't terribly wrong. Do I need to somehow "import" the Summation type somewhere?


Solution

  • This error usually means that you are creating a quotation that contains a value of custom type. The quotations in type providers can only contain values of primitive types - the compiler knows how to serialize these - but it cannot handle custom types.

    In the snippet, this happens here:

    let result = QuickAdd.add num1 num2
    let orig = ProvidedProperty("Sample", typeof<Summation>, GetterCode = (fun args -> 
      <@@ result @@>))
    

    Here, the GetterCode returns a quotation containing value of type Summation which is not supported. To make this work, you can do various things - generally, you'll need to come up with some other quoted expression that produces the value you want.

    One option is to do the calculation inside the quotation rather than outside:

    <@@ QuickAdd.add num1 num2 @@>
    

    The other option would be to re-create the Summation value in the quotation:

    let (Summation n) = result
    <@@ Summation n @@>
    

    This works, because it only needs to serialize a primitive int value and then generate a call to the Summation case constructor.