Skip to content

Instantly share code, notes, and snippets.

@takemyoxygen
Last active April 20, 2016 20:09
Show Gist options
  • Save takemyoxygen/d6d3a961dca1a8f161abcc13e02fe590 to your computer and use it in GitHub Desktop.
Save takemyoxygen/d6d3a961dca1a8f161abcc13e02fe590 to your computer and use it in GitHub Desktop.
namespace GenericMethodsTypeProvider
open System.Reflection
open System.IO
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open ProviderImplementation
open ProviderImplementation.ProvidedTypes
module Printer =
let print (name: string) (value: 'T) =
printfn "%s: %O" name value
module Provided =
let property name propertyType value =
ProvidedProperty(name, propertyType, GetterCode = (fun _ -> value))
let ctor () = ProvidedConstructor([], InvokeCode = (fun _ -> <@@ () @@>))
[<TypeProvider>]
type SomeTypeProviderFactory(config : TypeProviderConfig) as this=
inherit TypeProviderForNamespaces()
let ns = "Namespace"
let asm = Assembly.LoadFrom config.RuntimeAssembly
do
let providerTempAssembly = Path.ChangeExtension(Path.GetTempFileName(), ".dll") |> ProvidedAssembly
let provider = ProvidedTypeDefinition(asm, ns, "Provider", Some typeof<obj>, IsErased = false, HideObjectMethods = true)
// Create empty "AnotherType" with parameterless constructor
let another = ProvidedTypeDefinition("AnotherType", Some typeof<obj>, IsErased = false)
another.AddMember <| Provided.ctor()
provider.AddMember another
let target = ProvidedTypeDefinition("TargetType", Some typeof<obj>, IsErased = false)
let properties = [
Provided.property "Id" typeof<int> <| Expr.Value 10;
Provided.property "Name" typeof<string> <| Expr.Value "abc";
Provided.property "MaybeAge" typeof<int option> <| Expr.Value null
Provided.property "MaybeAnotherProp" (typedefof<option<_>>.MakeGenericType(another)) <| Expr.Value null
// Provided.property "AnotherProp" another <| Expr.Value null
]
target.AddMembers properties
target.AddMember <| ProvidedMethod("PrintMembers", [], typeof<unit>, InvokeCode = (fun args ->
let this = args.[0]
// MethodInfo of Printer.print<'T> method definition where concrete type of 'T is not specified
let printMethod =
match <@@ Printer.print "foo" 42 @@> with
| Call(_, x, _) -> x.GetGenericMethodDefinition()
let printStatements =
properties
|> List.map (fun property ->
try
if property.PropertyType.IsGenericType &&
property.PropertyType.GetGenericTypeDefinition() = typedefof<option<_>>
then
let isSomeProp = property.PropertyType.GetProperty("IsSome")
let valueProp = property.PropertyType.GetProperty("Value")
let underlyingType = property.PropertyType.GenericTypeArguments.[0]
let genericPrint = printMethod.MakeGenericMethod(underlyingType)
let propertyName = property.Name
let isSome = Expr.PropertyGet(isSomeProp, [Expr.PropertyGet(this, property)])
let getValue = Expr.PropertyGet(Expr.PropertyGet(this, property), valueProp)
let printPropertyValue = Expr.Call(genericPrint, [Expr.Value(propertyName); getValue])
Expr.IfThenElse(isSome, printPropertyValue,<@@ Printer.print propertyName "Absolutely nothing" @@>)
else
let genericPrint = printMethod.MakeGenericMethod(property.PropertyType)
Expr.Call(genericPrint, [Expr.Value(property.Name); Expr.PropertyGet(this, property)])
with
| ex ->
printfn "Failed to generage call of Printer.print for property %s of type %O. Error details: %O" property.Name property.PropertyType ex
reraise())
// Generates sequence of expressions that call Printer.print for each property
List.foldBack (fun t acc -> Expr.Sequential(t, acc)) printStatements <| Expr.Value(())))
target.AddMember <| Provided.ctor()
provider.AddMember target
providerTempAssembly.AddTypes [provider]
this.AddNamespace(ns, [provider])
[<assembly:TypeProviderAssembly>]
do()
#r "../build/GenericMethodsTypeProvider.dll"
type Provider = Namespace.Provider
let f = Provider.Target()
f.PrintMembers()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment