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

Generate type provider fails to create custom operation for computation expression #386

Open DragonSA opened 1 year ago

DragonSA commented 1 year ago

Description

Using a generative type provider fails to create a computation expression builder with a custom operation. The compiler fails to recognise the method as a custom operation.

Note that the IL appears to be practically equivalent for the generated type and the manually created builder (that does compile successfully).

Repro steps

See the repo ProvidedCustomOperation for a minimal reproduction. Key snippets below:

The generative type provider that creates a computation expression builder with a custom operation defined.

[<TypeProvider>]
type Provider(config) as this =
    inherit TypeProviderForNamespaces(config)

    let ns = "Provider"

    do
        let provider = ProvidedTypeDefinition(Assembly.GetExecutingAssembly(), ns, "BuilderProvider", Some typeof<obj>, isErased = false)
        provider.DefineStaticParameters([ ProvidedStaticParameter("_", typeof<bool>) ], fun typeName _ ->
            let asm = ProvidedAssembly()
            let builder = ProvidedTypeDefinition(asm, ns, typeName, Some typeof<CommonBuilder>, isErased = false)
            builder.AddMember(ProvidedConstructor([ ], fun _ -> <@@ () @@>))
            let method = ProvidedMethod(
                methodName = "Add",
                parameters = [ ProvidedParameter("x", typeof<int list>); ProvidedParameter("a", typeof<int>) ],
                returnType = typeof<int list>,
                invokeCode = (fun [ _; x; a ] -> <@@ ((%%a : int) + 5)::(%%x : int list) @@>))
            method.AddCustomAttribute({
                new CustomAttributeData() with
                    member _.Constructor = typeof<CustomOperationAttribute>.GetConstructor([| typeof<string> |])
                    member _.ConstructorArguments = [| CustomAttributeTypedArgument(typeof<string>, "add") |]
                    member _.NamedArguments = [| CustomAttributeNamedArgument(typeof<CustomOperationAttribute>.GetProperty("MaintainsVariableSpaceUsingBind"), true) |]
            })
            builder.AddMember(method)
            asm.AddTypes([ builder ])
            builder)
        this.AddNamespace(ns, [ provider ])

The equivalent builder:

type ExplicitBuilder() =
    inherit CommonBuilder()

    [<CustomOperation("add", MaintainsVariableSpaceUsingBind=true)>]
    member _.Add(x, a) = (a + 5)::x

Expected behavior

The computation expression compiles successfully:

type ProvidedBuilder = BuilderProvider<false>
let y = ProvidedBuilder()
assert (y {
    add 1
    add 2
    yield 3
} = [7; 6; 3])

Actual behavior

Compilation error:

ProvidedCustomOperation/Usage/Program.fs(12,5): error FS0039: The value or constructor 'add' is not defined. [ProvidedCustomOperation/Usage/Usage.fsproj]
ProvidedCustomOperation/Usage/Program.fs(13,5): error FS0039: The value or constructor 'add' is not defined. [ProvidedCustomOperation/Usage/Usage.fsproj]

Related information

dsyme commented 1 year ago

This is not currently supported by the F# tooling. In particular TryBindMethInfoAttribute is incomplete for provided methods:

From fsharp\src\Compiler\Checking\AttributeChecking.fs:

let TryBindMethInfoAttribute g (m: range) (AttribInfo(atref, _) as attribSpec) minfo f1 f2 f3 = 
#if NO_TYPEPROVIDERS
    // to prevent unused parameter warning
    ignore f3
#endif
    BindMethInfoAttributes m minfo 
        (fun ilAttribs -> TryDecodeILAttribute atref ilAttribs |> Option.bind f1)
        (fun fsAttribs -> TryFindFSharpAttribute g attribSpec fsAttribs |> Option.bind f2)
#if !NO_TYPEPROVIDERS
        (fun provAttribs -> 
            match provAttribs.PUntaint((fun a -> a.GetAttributeConstructorArgs(provAttribs.TypeProvider.PUntaintNoFailure(id), atref.FullName)), m) with
            | Some args -> f3 args
            | None -> None)  
#else
        (fun _provAttribs -> None)
dsyme commented 1 year ago

If you'd like to help address this problem, please submit a PR to dotnet/fsharp