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

Wrong namespace for generative TypeProviders #384

Open thinkbeforecoding opened 2 years ago

thinkbeforecoding commented 2 years ago

Description

The WsdlProvider is a generative provider due to the use of WCF. To implement Wsdl Choice elements, some properties must have an attribute referencing the Type of provided classes.

When generating the typeof, the namespace emitted is the generic one used in the TP, but the type is actually emitted in the user's code namespace. When used, the actual type cannot be found.

Repro steps

  1. Create a generative TP type provider
  2. In the type generation, create a ProvidedTypeDefinition call MyObject in TpNS namespace
  3. In a provided function, emit a typeof this type.
  4. Write a sample that use the type provider in a Sample namespace

Expected behavior

When running the sample, the type should be found in the Sample namespace

Actual behavior

The typeof jitting will fail, indicating that the type TpNS.MyObject cannot be found. The class is actually in Sample.MyObject.

Known workarounds

There is no known workaround.

Is there a way to get the actual namespace where the generated code will be compiled ? Is this information available in the TypeProvider ?

Related information

thinkbeforecoding commented 2 years ago

I checked the IL ecma spec, and it seems everywhere else, types are referenced by index in a table, but in the case of attributes, the attribute blob data contains the name of the attribute... Except that once injected in the destination, the inner namespace used to create ProvidedTypeDefinition isn't the actual type namespace. An error is raised as soon as you try to load the type as the attribute type is not found.

It doesn't seem that the destination namespace is accessible in the type provider so it seems not doable... Am I wrong?

thinkbeforecoding commented 2 years ago

The problem occurs with the 'choice' implementation in the WsdlProvider.

The wsdl can contain a complex type that C that has a field that can be different types based on the xlm namespace/name. For instance we have type C that can contain either A or B.

The type provider should emit something that look like this:

namespace MyNamespace
type MyService() =
     ...
     type A() = ... // nested type A
     type B() = ... // nested type B
     type C() = 
           [<XmlElement("a", "namespace_a", typeof<MyNamespace.MyService.A> >]
           [<XmlElement("b", "namespace_b", typeof<MyNamespace.MyService.B> >]
           member _.Item : obj
          ....

Except that there is no way to get "MyNamespace" during compilation, so it emits "FSharp.Data.MyService.A" where FSharp.Data is the namespace used to build ProvidedTypeDefinition ... When using C, the type loading fails because type FSharp.Data.MyService.A doesn't exist 😓

thinkbeforecoding commented 2 years ago

so it works as long as you reference external types. but not when using provided types.

dsyme commented 2 years ago

Yes this difference in encoding for type references in attributes has been a bane to rewriting tools before, it is certainly the problem here

dsyme commented 2 years ago

I think it is the rewrite phase in the compiler for generated assemblies which is missing this. I'll take a look if its a simple fix (though will take a while to get through the works even if it is)