FSharp.TypeProviders.SDK icon indicating copy to clipboard operation
FSharp.TypeProviders.SDK copied to clipboard

Generate type provider fails to create custom operation for computation expression

Open DragonSA opened this issue 3 years ago • 2 comments

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

  • Operating system: Darwin Davids-iMac.local 20.6.0 Darwin Kernel Version 20.6.0: Tue Jun 21 20:50:28 PDT 2022; root:xnu-7195.141.32~1/RELEASE_X86_64 x86_64
  • Branch: NuGet 7.0.3
  • .NET Runtime: 6.0.301

DragonSA avatar Aug 07 '22 09:08 DragonSA

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 avatar Aug 23 '22 14:08 dsyme

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

dsyme avatar Aug 23 '22 14:08 dsyme