The F# Type Provider SDK
The F# Type Provider SDK is two things:
ProvidedTypes.fsAPI files you need to author type providers
Documentation and samples on type provider creation
This package is actively seeking contributions.
The ProvidedTypes API - Adding the Files
Building a type provider nearly always starts with adding these files to your project:
Type providers may be used in projects that generate .NET Standard code or target other .NET Frameworks than that being used to execute the F# compiler.
The ProvidedTypes API - A Basic Type Provider
Here is a basic erasing type provider using the Provided Types API:
open ProviderImplementation open ProviderImplementation.ProvidedTypes open Microsoft.FSharp.Core.CompilerServices open System.Reflection [<TypeProvider>] type BasicProvider (config : TypeProviderConfig) as this = inherit TypeProviderForNamespaces (config) let ns = "StaticProperty.Provided" let asm = Assembly.GetExecutingAssembly() let createTypes () = let myType = ProvidedTypeDefinition(asm, ns, "MyType", Some typeof<obj>) let myProp = ProvidedProperty("MyProperty", typeof<string>, isStatic = true, getterCode = (fun args -> <@@ "Hello world" @@>)) myType.AddMember(myProp) [myType] do this.AddNamespace(ns, createTypes()) [<assembly:TypeProviderAssembly>] do ()
Some unit testing helpers
The SDK includes a file
which is sometimes incorporated into a type provider to help enable unit testing. For examples of how this is used, see uses of the helpers in the FSharp.Data library such as
Targets.DotNet45FSharp40Refs()to get a suitable set of references for .NET 4.5, F# 4.0 target on a typical Mono/.NET Framework installation
Testing.FormatProvidedTypeto get a textual representation of a provided type, used to "snapshot" the full description of expected type generation
Sometimes unit test DLLs incorporate the entire type provider implementation, and sometimes they use InternalsVisibleTo.
The unit testing helpers aren't really an official, documented part of the DK - caveat emptor.
See examples the
- ComboProvider: the TPDTC and TPRTC are combined together in one assembly which is a single
- BasicProvider: the TPDTC and TPRTC are each available as both
Using Type Providers with
Correctly updated type providers can be used with either the
dotnet toolchain (.NET SDK tools which executes using .NET Core) or
msbuild (traditional .NET Framework/Mono) toolchain.
For .NET SDK 2.1.4 and before, see How to enable type providers with new-style .NET SDK project files,
dotnet build, .NET Standard and .NET Core programming
For .NET SDK 2.1.100 and above, you can either use type providers specifically updated to work with the .NET SDK, or use the same workaround.
Updating a Type Provider to be suitable for use with the .NET SDK
This short guide assumes
- You have a type provider with separate TPDTC and TPRTC components (see below if you don't know what those are)
- Some of your code might have dependencies on .NET Framework functionality
- You want your type provider to be usable with both the
dotnettoolchain (.NET SDK tools which executes using .NET Core) or
msbuild(traditional .NET Framework/Mono) toolchain.
- You want your type provider to be usable for all of .NET Standard, .NET Core and .NET Framework programming (if possible)
Here is a guide to the steps to perform:
Use .NET SDK 2.1.100-preview-007363 or above. Forget .NET SDK 2.1.4 and before.
If using Visual Studio, then use Visual Studio 2017 15.6 and above. Your type provider will still be usable with previous versionss, we'll get to that, but for now assume 15.6
First switch to use .NET SDK project files, compiling them with
Update to the latest ProvidedTypes.fs/fsi from this project
Work out how much your TPRTC (runtime component) depends on .NET Framework by trying to target
netstandard2.0. You may need to use different package references to try this.
If your TPRTC fundamentally depends on .NET Framework, then you will not be able to use your type provider within projects targeting .NET Core or .NET Standard. Keep targeting your TPRTC at .NET Framework.
If your TPRTC partially depends on .NET Framework, then multi-target the TPRTC to
If your TPRTC doesn't depend on .NET Framework, then target the TPRTC to
Work out how much of a dependency your TPDTC has on .NET Framework:
If the compile-time computations performed by your TPDTC fundamentally depend on .NET Framework, then your type provider will not be usable with the .NET SDK toolchain without using the workaround)
If the TPDTC partially depends on .NET Framework, then multi-target the TPDTC to
If the TPDTC doesn't depend on .NET Framework, then target the TPDTC to
Beware that your TPDTC might have a false dependency induced by including a copy of the TPRTC source code into the TPDTC (which is generally a good technique). It is likely such a dependency can be removed by selectively stubbing out runtime code using a
IS_DESIGNTIMEdefine. The TPDTC only needs access to an "API" that has the same logical shape as the TPRTC in order to generate code and types. That "API" is then translated to match the targret references assemblies in an actual compilation.
Modify your project to copy the design-time DLLs into the right place, e.g. see this example
Have your test projects multi-target to
dotnet buildto build instead of
- If any of your projects targeting .NET 4.x so they will compile with
dotneton Linux/OSX when Mono is installed, then include netfx.props in the project and project file
- If any of your projects targeting .NET 4.x so they will compile with
Modify your nuget package layout as described below.
Nuget package layouts you should use
The typical nuget package layout for a provider that has combined design-time and runtime components is:
lib/netstandard2.0 MyProvider.dll // TPRTC and TPDTC netstandard.dll // Extra facade, see below System.Runtime.dll // Extra facade, see below System.Reflection.dll // Extra facade, see below
The typical nuget package layout for a provider that has separate design-time and runtime components is:
lib/net45/ MyProvider.dll // TPRTC MyProvider.DesignTime.dll // .NET 4.x TPDTC alongside TPRTC for legacy loading lib/typeproviders/fsharp41/ net45/ MyProvider.DesignTime.dll // .NET 4.x TPDTC netstandard2.0/ MyProvider.DesignTime.dll // .NET Standard 2.0 TPDTC netstandard.dll // Extra facade, see below System.Runtime.dll // Extra facade, see below System.Reflection.dll // Extra facade, see below
It is important that the design-time assemblies you use (if any) are not loaded at runtime. To ensure this does not happen, when you distribute a Nuget package for your Type Provider you must provide an explicit list of project references for consumers to include. If you do not, every assembly you publish in the package will be included, which can lead to design-type only references being loaded at runtime. To reference only a subset of assemblies, see the Nuget documetation or the Paket documentation.
That is, an explicit
.nuspec file will be needed with an explicit
<references> node (so that only the TPRTC gets added as a reference), see this example.
Some Type Provider terminology
TPRTC - Type Provider Referenced Component, e.g.
This is the component referenced by
-r:on the command line or other confugration of a host tool
May be the same physical file as the TPDTC.
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.
A type provider package may have multiple such DLLs for different target platforms, e.g.
TPRTCs are normally .NET Framework 4.x, .NET Standard 2.0 or some portable profile component.
TPDTC - Type Provider Design Time Component, e.g.
The DLL that gets loaded into host tools.
May be the same physical file as the TPRTC.
This component includes the ProvidedTypes.fs/fsi files from the type provider SDK.
TPDTCs are currently .NET Framework 4.x. They can also be .NET Standard 2.0 components, see below
Host tool - Either
fsi.exeor some tool hosting
How the TPDTC is found and loaded
Currently, host tools look for TPDTC DLLs alongside the TPRTC DLL. For simple type providers, these DLLs are the same. When executing using .NET Framework, the host tool uses
Assembly.LoadFrom to load this component.
See Type provider design-time DLLs should be chosen more appropriately for a proposal to change the rules to allow TPDTC components to be found more usefully, and in particular for different TPDTC components to be loaded depending on the execution environment of the host tooling.
Making a .NET Standard 2.0 TPDTC
It will be increasingly common to make type providers where the TPDTC is a .NET Standard 2.0 component. In the very simplest case, there will just be one happy .NET Standard 2.0 component
MyTypeProvider.dll acting as both the TPDTC and TPRTC. Such a type provider will eventually be loadable into all F# tooling.
However, today, for a TPDTC to be .NET Standard 2.0, it must be loadable into host tools using .NET Framework 4.6.1 or Mono 5.x, the most common platforms for execution of F# tooling. Because .NET Framework 4.6.1 doesn't fully support .NET Standard 2.0, this can only be done if the TPDTC ships alongside some facade DLLs. Currently the following facade DLLs are needed alongside the TPDTC:
<!-- These files are the facades necessary to run .NET Standard 2.0 components on .NET Framework 4.6.1 (.NET Framework 4.7 will --> <!-- come with these facades included). Because the type provider is a .NET Standard 2.0 component, the deployment of the type --> <!-- provider must include these facade DLLs if it is to run hosted inside an F# compiler executing using .NET Framework 4.6.1 or Mono 5.0. --> <None Include="..\..\packages\NetStandard.Library.NetFramework\build\net461\lib\netstandard.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="..\..\packages\NetStandard.Library.NetFramework\build\net461\lib\System.Reflection.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="..\..\packages\NetStandard.Library.NetFramework\build\net461\lib\System.Runtime.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None>
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
- Always use
ProvidedTypeBuilder.MakeGenericType(type, typeArguments)rather than
- Always use
ProvidedTypeBuilder.MakeGenericMethod(methInfo, methTypeArguments)rather than
- Where necessary open
open ProviderImplementation.ProvidedTypes.UncheckedQuotationsand make quotation nodes representing calls and other operations using
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
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
For advice on how to get started building a type provider, check out:
- Type Providers from the ground up
- (and the follow up posts)
- The MSDN Tutorial. The code in this package replaces the code from the sample pack it mentions.
Support and community
- If you have a question about
FSharp, ask at StackOverflow and mark your question with the
- If you want to submit a bug, a feature request or help with fixing bugs then look at issues.
- To discuss more general issues about F# Type Providers SDK, its goals and other open-source F# projects, join the fsharp-opensource mailing list
The library is available under the MIT License. For more information see the License file in the GitHub repository.
The default maintainer account for projects under "fsprojects" is @fsprojectsgit - F# Community Project Incubation Space (repo management)