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

Constructor exception with external dependency #263

Closed juselius closed 2 years ago

juselius commented 5 years ago

Description

I'm trying to generate a type based on json. I can generate a type which accesses the json at runtim (i.e. from inside a quotation). However, if I try to use information from json to generate the type, I get an exception.

I suspect the problem is related to resolution of the Newtonsoft.Json assembly when the provider is created. I have tried to copy the .dll to the output directory, adding a resolve event handler, registering probing folders, to no avail.

It does not matter if the result from JsonConvert is actually used, it's enough that it gets called. Commenting it out and returning a hand crafted Acme works fine.

Repro steps

Referencing the following code in a .fsproj gives an exception:

module AcemeProvider

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

type Acme = { Foo : string }

let getAcme () = 
    let json = """ { "Foo": "Bar" } """
    JsonConvert.DeserializeObject<Acme> json

[<TypeProvider>]
type BasicProvider (config: TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces(config, addDefaultProbingLocation = true)

    let asm = Assembly.GetExecutingAssembly()
    let ns = "Acme.Provided"
    let create name =
        let a = getAcme ()
        let t = ProvidedTypeDefinition(asm, ns, a.Foo, Some typeof<obj>)
        let ctor = ProvidedConstructor([],  invokeCode = fun _ -> <@@ obj () @@>)
        t.AddMember(ctor)
        [t]

    do this.AddNamespace(ns, create "MyAcme")

[<assembly:TypeProviderAssembly>]
do ()

Expected behavior

I expect the code to compile.

Actual behavior

error FS3053 : The type provider 'AcemeProvider+BasicProvider' reported an error : The type provider constructor has thrown an exception: Exception has been thrown by the target of an invocation.

Known workarounds

None

Related information

juselius commented 5 years ago

I got it to work! If I define a static parameter, and copy Newtonsoft.Json.dll to the output directory of the type provider project (not runtime), then everything works like expected. No matter what I try, I cannot get the original example to work.

Here is a minimal working example:

module AcemeProvider

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

type Acme = { Foo : string }

let getAcme f = 
    let json = """ {"Foo": "Bar"} """
    JsonConvert.DeserializeObject<Acme> json

[<TypeProvider>]
type BasicProvider (config: TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces(config, addDefaultProbingLocation = true)

    let asm = Assembly.GetExecutingAssembly()

    let ns = "Acme.Provided"

    let staticParams = [
        ProvidedStaticParameter("file", typeof<string>)
        ]

    let _t = ProvidedTypeDefinition(asm, ns, "OinkOink", None)
    do _t.DefineStaticParameters (
        parameters = staticParams,
        instantiationFunction = (fun typeName paramValues ->
            match paramValues with
            | [| :? string as file |] ->
                let t = 
                    ProvidedTypeDefinition(asm, ns, typeName, Some typeof<Acme>)
                let ctor = 
                    ProvidedConstructor(
                        parameters = [], 
                        invokeCode = fun args -> <@@ getAcme file @@>
                    )

                let t1 = 
                    let a = getAcme file
                    ProvidedTypeDefinition( asm, ns, a.Foo, Some typeof<Acme>)
                let ctor1 = 
                    ProvidedConstructor(
                        parameters = [], 
                        invokeCode = fun args -> <@@ getAcme file @@>
                    )
                t1.AddMember ctor1

                t.AddMember ctor
                t.AddMember t1
                t
            | _ -> failwith "Error in error message"
        )
    )
    do this.AddNamespace(ns, [_t])

[<assembly:TypeProviderAssembly>]
do ()
dsyme commented 5 years ago

Ah interesting, this muse have to do with exactly when the default probing location is added...

juselius commented 5 years ago

I don't know if it's related, but when compiling the type provider together with WebSharper (4.x) server-side, the second version also fails to load dependencies. WebSharper wraps fsc.exe in wsfsc.exe, and I have not looked at the details yet.

dsyme commented 2 years ago

Closing this old issue