Skip to content

Commit 939a4c8

Browse files
[Repo Assist] Fix generative delegate type support; implement GetInterface on ProvidedTypeDefinition (#479)
🤖 *This is an automated PR from Repo Assist, an AI assistant for this repository.* ## Summary Two related fixes for correctness gaps in the SDK. --- ### Bug fix: Generative delegate types were unsupported (Task 3) Attempting to create a generative `ProvidedTypeDefinition` that extends `System.MulticastDelegate` would previously fail at assembly-writing time with: > *"The provided method is not marked as an abstract method; therefore, it should define an implementation."* The root cause: .NET delegate types are special. Their `.ctor(object, nativeint)` and `Invoke`/`BeginInvoke`/`EndInvoke` methods must carry **`MethodImplAttributes.Runtime | MethodImplAttributes.Managed`** — the CLR synthesises their bodies at JIT time, and no IL body is ever written in the binary. The old assembly writer had no path for this case. **Changes in `ProvidedTypes.fs`:** - Add `ILMethodBuilder.SetImplementationFlags` to expose impl-attribute control - In assembly writer **phase 2**, detect delegate types (`BaseType.FullName = "System.MulticastDelegate"`) and set `Runtime|Managed` impl flags on all their constructors and methods - In assembly writer **phase 3**, skip IL body emission for delegate type constructors and methods (the CLR synthesises them) Example usage that now works: ````fsharp let myHandler = ProvidedTypeDefinition("MyHandler", Some typeof(System.MulticastDelegate), isErased = false) myHandler.AddMember( ProvidedConstructor( [ ProvidedParameter("object", typeof(obj)) ProvidedParameter("method", typeof(nativeint)) ], invokeCode = fun _ -> <@@ () @@>)) // invokeCode is ignored for delegate types myHandler.AddMember( ProvidedMethod("Invoke", [ ProvidedParameter("sender", typeof(obj)) ProvidedParameter("e", typeof(EventArgs)) ], typeof(Void))) ``` --- ### Coding improvement: `GetInterface` now works instead of throwing (Task 5) `ProvidedTypeDefinition.GetInterface(name, ignoreCase)` and `TargetTypeDefinition.GetInterface(name, ignoreCase)` previously threw `NotSupportedException`. They now search through `GetInterfaces()`, matching on `Name` (short name) or `FullName` (dotted name), consistent with how `TypeDelegator` implements this in .NET. --- ### Tests Added `tests/GenerativeDelegateTests.fs` with **5 new tests**: | Test | What it verifies | |------|-----------------| | `Generative delegate type is present in generated assembly` | Type exists with `MulticastDelegate` as base | | `Generative delegate type has correct constructor` | `.ctor(object, nativeint)` present with correct param types | | `Generative delegate Invoke method has correct signature` | `Invoke(object, EventArgs): void` | | `Generative delegate with value return type has correct Invoke signature` | `Invoke(int, int): int` | | `Multiple delegate types can coexist in one container` | Both delegate types present; both have `MulticastDelegate` base | ## Test Status All **122 tests pass** (117 pre-existing + 5 new), `net8.0`. ``` Passed! - Failed: 0, Passed: 122, Skipped: 0, Total: 122 ```` > Generated by [Repo Assist](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/23273901481) · [◷](https://github.com/search?q=repo%3Afsprojects%2FFSharp.TypeProviders.SDK+%22gh-aw-workflow-id%3A+repo-assist%22&type=pullrequests) > > To install this [agentic workflow](https://github.com/githubnext/agentics/tree/346204513ecfa08b81566450d7d599556807389f/workflows/repo-assist.md), run > ``` > gh aw add githubnext/agentics@3462045 > ``` <!-- gh-aw-agentic-workflow: Repo Assist, engine: copilot, id: 23273901481, workflow_id: repo-assist, run: https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/23273901481 --> <!-- gh-aw-workflow-id: repo-assist --> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d2baca0 commit 939a4c8

3 files changed

Lines changed: 163 additions & 3 deletions

File tree

src/ProvidedTypes.fs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,9 +1657,15 @@ and ProvidedTypeDefinition(isTgt: bool, container:TypeContainer, className: stri
16571657
let xs = this.GetNestedTypes bindingFlags |> Array.filter (fun m -> m.Name = name)
16581658
if xs.Length > 0 then xs.[0] else null)
16591659

1660-
override __.GetInterface(_name, _ignoreCase) = notRequired this "GetInterface" this.Name
1660+
override __.GetInterface(name, ignoreCase) =
1661+
let sc = if ignoreCase then StringComparison.OrdinalIgnoreCase else StringComparison.Ordinal
1662+
this.GetInterfaces()
1663+
|> Array.tryFind (fun t ->
1664+
if name.Contains(".") then String.Equals(t.FullName, name, sc)
1665+
else String.Equals(t.Name, name, sc))
1666+
|> Option.toObj
16611667

1662-
override __.GetInterfaces() = getInterfaces()
1668+
override __.GetInterfaces() = getInterfaces()
16631669

16641670

16651671
override __.MakeArrayType() = ProvidedTypeSymbol(ProvidedTypeSymbolKind.SDArray, [this], typeBuilder) :> Type
@@ -8141,7 +8147,13 @@ namespace ProviderImplementation.ProvidedTypes
81418147
override this.GetCustomAttributes(_inherited) = notRequired this "GetCustomAttributes" inp.Name
81428148
override this.GetCustomAttributes(_attributeType, _inherited) = notRequired this "GetCustomAttributes" inp.Name
81438149
override this.IsDefined(_attributeType, _inherited) = notRequired this "IsDefined" inp.Name
8144-
override this.GetInterface(_name, _ignoreCase) = notRequired this "GetInterface" inp.Name
8150+
override this.GetInterface(name, ignoreCase) =
8151+
let sc = if ignoreCase then StringComparison.OrdinalIgnoreCase else StringComparison.Ordinal
8152+
this.GetInterfaces()
8153+
|> Array.tryFind (fun t ->
8154+
if name.Contains(".") then String.Equals(t.FullName, name, sc)
8155+
else String.Equals(t.Name, name, sc))
8156+
|> Option.toObj
81458157
override this.GetElementType() = notRequired this "GetElementType" inp.Name
81468158
override this.InvokeMember(_name, _invokeAttr, _binder, _target, _args, _modifiers, _culture, _namedParameters) = notRequired this "InvokeMember" inp.Name
81478159

@@ -13964,6 +13976,7 @@ namespace ProviderImplementation.ProvidedTypes
1396413976
member __.DefineGenericParameter(name, attrs) = let eb = ILGenericParameterBuilder(name, attrs) in gparams.Add eb; eb
1396513977
member __.DefineParameter(i, attrs, parameterName) = ilParams.[i].SetData(attrs, parameterName) ; ilParams.[i]
1396613978
member __.SetCustomAttribute(ca) = cattrs.Add(ca)
13979+
member __.SetImplementationFlags(f: MethodImplAttributes) = implflags <- f
1396713980
member __.GetILGenerator() = let ilg = ILGenerator(methodName) in body <- Some ilg; ilg
1396813981
member __.FormalMethodRef =
1396913982
let cc = (if ILMethodDef.ComputeIsStatic attrs then ILCallingConv.Static else ILCallingConv.Instance)
@@ -15760,6 +15773,7 @@ namespace ProviderImplementation.ProvidedTypes
1576015773
match ptdT with
1576115774
| None -> ()
1576215775
| Some ptdT ->
15776+
let isDelegateType = ptdT.BaseType <> null && ptdT.BaseType.FullName = "System.MulticastDelegate"
1576315777
for cinfo in ptdT.GetConstructors(bindAll) do
1576415778
match cinfo with
1576515779
| :? ProvidedConstructor as pcinfo when not (ctorMap.ContainsKey pcinfo) ->
@@ -15772,6 +15786,9 @@ namespace ProviderImplementation.ProvidedTypes
1577215786
for (i, p) in cinfo.GetParameters() |> Seq.mapi (fun i x -> (i, x)) do
1577315787
cb.DefineParameter(i+1, ParameterAttributes.None, p.Name) |> ignore
1577415788
cb
15789+
// Delegate constructors use Runtime implementation; they have no IL body
15790+
if isDelegateType then
15791+
cb.SetImplementationFlags(MethodImplAttributes.Runtime ||| MethodImplAttributes.Managed)
1577515792
ctorMap.[pcinfo] <- cb
1577615793
| _ -> ()
1577715794

@@ -15819,6 +15836,9 @@ namespace ProviderImplementation.ProvidedTypes
1581915836

1582015837
pb.SetConstant p.RawDefaultValue
1582115838

15839+
// Delegate methods use Runtime implementation; they have no IL body
15840+
if isDelegateType then
15841+
mb.SetImplementationFlags(MethodImplAttributes.Runtime ||| MethodImplAttributes.Managed)
1582215842
methMap.[pminfo] <- mb
1582315843

1582415844
| _ -> ()
@@ -15834,6 +15854,10 @@ namespace ProviderImplementation.ProvidedTypes
1583415854

1583515855
defineCustomAttrs tb.SetCustomAttribute (ptdT.GetCustomAttributesData())
1583615856

15857+
// Delegate types (base = System.MulticastDelegate) use Runtime implementation; their
15858+
// constructor and Invoke/BeginInvoke/EndInvoke bodies are synthesised by the CLR.
15859+
let isDelegateType = ptdT.BaseType <> null && ptdT.BaseType.FullName = "System.MulticastDelegate"
15860+
1583715861
// Allow at most one constructor, and use its arguments as the fields of the type
1583815862
let ctors =
1583915863
ptdT.GetConstructors(bindAll) // exclude type initializer
@@ -15859,6 +15883,9 @@ namespace ProviderImplementation.ProvidedTypes
1585915883

1586015884
defineCustomAttrs cb.SetCustomAttribute (pcinfo.GetCustomAttributesData())
1586115885

15886+
// Delegate constructors have Runtime implementation; the CLR synthesises the body
15887+
if isDelegateType then () else
15888+
1586215889
let ilg = cb.GetILGenerator()
1586315890
let ctorLocals = Dictionary<Var, ILLocalBuilder>()
1586415891
let parameterVars =
@@ -15925,6 +15952,10 @@ namespace ProviderImplementation.ProvidedTypes
1592515952
[ for v in parameterVars -> Expr.Var v ]
1592615953

1592715954
match pminfo.GetInvokeCode with
15955+
| _ when isDelegateType ->
15956+
// Delegate methods (Invoke, BeginInvoke, EndInvoke) have Runtime implementation;
15957+
// the CLR synthesises their bodies. No IL is emitted.
15958+
()
1592815959
| Some _ when ptdT.IsInterface ->
1592915960
failwith "The provided type definition is an interface; therefore, it should not define an implementation for its members."
1593015961
| Some _ when pminfo.IsAbstract ->

tests/FSharp.TypeProviders.SDK.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<Compile Include="GenerativeEqualityComparisonTests.fs" />
2222
<Compile Include="GenerativeStructProvisionTests.fs" />
2323
<Compile Include="GenerativeEventsTests.fs" />
24+
<Compile Include="GenerativeDelegateTests.fs" />
2425
<Compile Include="ReferencedAssemblies.fs" />
2526
</ItemGroup>
2627
<ItemGroup>

tests/GenerativeDelegateTests.fs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
module TPSDK.GenerativeDelegateTests
2+
3+
#nowarn "760" // IDisposable needs new
4+
5+
open System
6+
open System.Reflection
7+
open Microsoft.FSharp.Core.CompilerServices
8+
open Xunit
9+
open ProviderImplementation.ProvidedTypes
10+
open ProviderImplementation.ProvidedTypesTesting
11+
12+
/// Type provider that creates a container type with two custom delegate types:
13+
/// - SimpleHandler : delegate void SimpleHandler(object sender, EventArgs e)
14+
/// - ValueHandler : delegate int ValueHandler(int x, int y)
15+
[<TypeProvider>]
16+
type GenerativeDelegatesProvider (config: TypeProviderConfig) as this =
17+
inherit TypeProviderForNamespaces (config)
18+
19+
let ns = "Delegates.Provided"
20+
let tempAssembly = ProvidedAssembly()
21+
let container = ProvidedTypeDefinition(tempAssembly, ns, "Container", Some typeof<obj>, isErased = false)
22+
23+
do
24+
// --- SimpleHandler: void(object, EventArgs) ---
25+
let simpleHandler = ProvidedTypeDefinition("SimpleHandler", Some typeof<System.MulticastDelegate>, isErased = false)
26+
simpleHandler.AddMember(
27+
ProvidedConstructor(
28+
[ ProvidedParameter("object", typeof<obj>)
29+
ProvidedParameter("method", typeof<nativeint>) ],
30+
invokeCode = fun _ -> <@@ () @@>))
31+
let invokeSimple = ProvidedMethod("Invoke",
32+
[ ProvidedParameter("sender", typeof<obj>)
33+
ProvidedParameter("e", typeof<EventArgs>) ],
34+
typeof<Void>)
35+
simpleHandler.AddMember invokeSimple
36+
container.AddMember simpleHandler
37+
38+
// --- ValueHandler: int(int, int) ---
39+
let valueHandler = ProvidedTypeDefinition("ValueHandler", Some typeof<System.MulticastDelegate>, isErased = false)
40+
valueHandler.AddMember(
41+
ProvidedConstructor(
42+
[ ProvidedParameter("object", typeof<obj>)
43+
ProvidedParameter("method", typeof<nativeint>) ],
44+
invokeCode = fun _ -> <@@ () @@>))
45+
let invokeValue = ProvidedMethod("Invoke",
46+
[ ProvidedParameter("x", typeof<int>)
47+
ProvidedParameter("y", typeof<int>) ],
48+
typeof<int>)
49+
valueHandler.AddMember invokeValue
50+
container.AddMember valueHandler
51+
52+
tempAssembly.AddTypes [container]
53+
this.AddNamespace(ns, [container])
54+
55+
let loadTestAssembly () =
56+
let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs()
57+
let runtimeAssembly = runtimeAssemblyRefs.[0]
58+
let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
59+
let tp = GenerativeDelegatesProvider(cfg) :> TypeProviderForNamespaces
60+
let providedNamespace = tp.Namespaces.[0]
61+
let providedType = providedNamespace.GetTypes().[0] :?> ProvidedTypeDefinition
62+
Assert.Equal("Container", providedType.Name)
63+
let bytes = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly)
64+
Assembly.Load bytes
65+
66+
[<Fact>]
67+
let ``Generative delegate type is present in generated assembly``() =
68+
let assembly = loadTestAssembly ()
69+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
70+
let delegateType = containerType.GetNestedType("SimpleHandler")
71+
Assert.NotNull(delegateType)
72+
Assert.True(delegateType.IsClass, "SimpleHandler should be a class")
73+
Assert.Equal("System.MulticastDelegate", delegateType.BaseType.FullName)
74+
75+
[<Fact>]
76+
let ``Generative delegate type has correct constructor``() =
77+
let assembly = loadTestAssembly ()
78+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
79+
let delegateType = containerType.GetNestedType("SimpleHandler")
80+
Assert.NotNull(delegateType)
81+
let ctor = delegateType.GetConstructor([| typeof<obj>; typeof<nativeint> |])
82+
Assert.NotNull(ctor)
83+
let ps = ctor.GetParameters()
84+
Assert.Equal(2, ps.Length)
85+
Assert.Equal(typeof<obj>, ps.[0].ParameterType)
86+
Assert.Equal(typeof<nativeint>, ps.[1].ParameterType)
87+
88+
[<Fact>]
89+
let ``Generative delegate Invoke method has correct signature``() =
90+
let assembly = loadTestAssembly ()
91+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
92+
let delegateType = containerType.GetNestedType("SimpleHandler")
93+
Assert.NotNull(delegateType)
94+
let invoke = delegateType.GetMethod("Invoke")
95+
Assert.NotNull(invoke)
96+
let ps = invoke.GetParameters()
97+
Assert.Equal(2, ps.Length)
98+
Assert.Equal("sender", ps.[0].Name)
99+
Assert.Equal(typeof<obj>, ps.[0].ParameterType)
100+
Assert.Equal("e", ps.[1].Name)
101+
Assert.Equal(typeof<EventArgs>, ps.[1].ParameterType)
102+
Assert.Equal(typeof<Void>, invoke.ReturnType)
103+
104+
[<Fact>]
105+
let ``Generative delegate with value return type has correct Invoke signature``() =
106+
let assembly = loadTestAssembly ()
107+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
108+
let delegateType = containerType.GetNestedType("ValueHandler")
109+
Assert.NotNull(delegateType)
110+
Assert.Equal("System.MulticastDelegate", delegateType.BaseType.FullName)
111+
let invoke = delegateType.GetMethod("Invoke")
112+
Assert.NotNull(invoke)
113+
let ps = invoke.GetParameters()
114+
Assert.Equal(2, ps.Length)
115+
Assert.Equal(typeof<int>, ps.[0].ParameterType)
116+
Assert.Equal(typeof<int>, ps.[1].ParameterType)
117+
Assert.Equal(typeof<int>, invoke.ReturnType)
118+
119+
[<Fact>]
120+
let ``Multiple delegate types can coexist in one container``() =
121+
let assembly = loadTestAssembly ()
122+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
123+
let nested = containerType.GetNestedTypes()
124+
let names = nested |> Array.map (fun t -> t.Name) |> Array.sort
125+
Assert.Contains("SimpleHandler", names)
126+
Assert.Contains("ValueHandler", names)
127+
for t in nested do
128+
Assert.Equal("System.MulticastDelegate", t.BaseType.FullName)

0 commit comments

Comments
 (0)