FSharp.TypeProviders.SDK


Technical Notes

A type provider is simultaneously a tool and a library. There is a component that runs at compile-time (also called "design-time") and a component that runs at runtime. F# type providers are hosted by applications using FSharp.Compiler.Service.

First, some terminology:

The Type Provider Runtime Component (TPRTC)

This contains either a TypeProviderAssembly attribute indicating that this component is also a TPDTC, or TypeProviderAssembly("MyDesignTime.dll") attribute indicating that the name of the design time component.

TPRTCs are normally netstandard2.0 or above.

The Type Provider Design Time Component (TPDTC)

The Type Provider Design Time Component (TPDTC) is, for example, FSharp.Data.DesignTime.dll.

This is the DLL that gets loaded into host tools, and may be the same physical file as the TPRTC. This component includes the ProvidedTypes.fs/fsi files from the type provider SDK.

TPDTC are generally netstandard2.0 or netstandard2.1 components.

See Loading type providers for the rules to find TPDTC components.

Naming Conventions

The following guidance extends https://fsharp.github.io/2014/09/19/fsharp-libraries.html.

Good type provider naming examples:

Here are some examples of existing type providers that aren't too bad (they are clear) but could be renamed to follow the guidelines:

Nuget package layout

The nuget package layout of a type provider follows these rules:

  1. The TPRTCs go under lib
  2. The TPDTC goes under typeproviders/fsharp41
  3. ALl dependencies of the TPDTC are bundled with the TPDTC (except the framework and FSharp.Core)

For examples:

lib/net45
    FSharp.Data.dll
lib/netstandard2.0
    MyProvider.dll

typeproviders/fsharp41/netstandard2.0/
    MyProvider.DesignTime.dll // TPDTC
    MyDesignTimeDependency.dll // bundled dependencies of TPDTC

Dependencies

Runtime dependencies are often the same as design time dependencies for simple type providers. For more complex providers these can be different

These dependencies are packaged and managed differently

Lifetime of type provider instantiations

F# type providers are hosted by applications using FSharp.Compiler.Service. These notes describe the lifetime and typical resource usage of type provider instances for applications that incorporate FSharp.Compiler.Service (the host).

Each time the host application (e.g. devenv.exe) checks a file using type providers (e.g. containing JsonProvider<"...">), one or more new TP instantiations may be created, along with subsequent calls to ApplyStaticArguments.

The lifetime of TAST structures is as long as they are held in the IncrementalBuilder, or you hold on to FSharpCheckFileResults, or FSharpCheckProjectResults, or FSharpAssemblyContents.

Explicit construction of code: MakeGenericType, MakeGenericMethod and UncheckedQuotations

Some type providers need to build code via explicit calls to FSharp.Quotations.Expr.* rather than via quotation literals. Frequently, this is needed when code must instantiate generic methods or types. However, in some cases limitations of the F# quotations API are reached.

In these cases, follow these rules

  1. Always use ProvidedTypeBuilder.MakeGenericType(type, typeArguments) rather than type.MakeGenericType(typeArguments)
  2. Always use ProvidedTypeBuilder.MakeGenericMethod(methInfo, methTypeArguments) rather than methInfo.MakeGenericType(methTypeArguments)
  3. Where necessary open open ProviderImplementation.ProvidedTypes.UncheckedQuotations and make quotation nodes representing calls and other operations using Expr.CallUnchecked.

If you don't do this you may get errors like

    The type provider 'FSharp.Configuration.ConfigTypeProvider+FSharpConfigurationProvider' reported an error: Type mismatch when building 'args': invalid parameter for a method or indexer property. Expected 'System.Collections.Generic.IEnumerable`1[System.String]', but received type 'System.Collections.Generic.IEnumerable`1[System.String]'.�Parameter name: receivedType

or

    System.InvalidOperationException: the operation is not valid due to the current state of the object. at System.Reflection.MemberInfo.get_MetadataToken() in f:\dd\ndp\clr\src\BCL\system\reflection\memberinfo.cs:line 65