The interceptors branch generates code that uses C# 12 interceptors to produce the required instance for FromAction, FromFunc, and FromFunctionPointer methods. These interceptors will not work if the call-site in user code is nested inside a generic type or method.
private readonly struct FunctionPtr<T>(nint functionPtr) // obviously bad example to demonstrate the issue only
{
private readonly INativeAction<T> action = INativeAction<T>.FromFunctionPointer // << THIS CALL
(
functionPtr,
null,
CallingConvention.Winapi
);
public void Invoke(T t) => action.Invoke(t);
}
Right now, this call to FromFunctionPointer would cause a NotImplementedException to be thrown at runtime.
Proposed solution
This scenario is already detected during codegen, but the fix is not yet implemented. We cannot intercept this using closed type symbols discovered from the GenericSymbolReferenceTree, because the signatures of the closed types don't match the signature of the open type at the call-site.
Instead, the only approach we can take here is to perform runtime type-checking and dispatch. We do already know the possible types used in this compilation, thanks to the tree. Marshalling behaviors must be parsable at compile-time. Calling conventions that are not parsable at compile-time currently default to CallingConvention.Winapi (the system default calling convention). Ergo, we do have all necessary details to dispatch these calls.
When building the ClassDescriptorCollection, we should check whether the call-site falls into this category. If it does, then we should create a separate collection so we know what classes to generated, and omit producing the interceptor. This separate collection will need to access the generated class name (which may be shared with other call-sites). This collection can use normal equality comparison for the MethodReference (rather than the equality comparer used in building the ClassDescriptorCollection).
Finally, after the codegen for the classes has been completed, we should enumerate this new collection and produce a single interceptor that performs the runtime type-checking.
Additional considerations
This should be considered as a primary use-case. This issue should be treated as high priority.
Describe the issue
The
interceptors
branch generates code that uses C# 12 interceptors to produce the required instance forFromAction
,FromFunc
, andFromFunctionPointer
methods. These interceptors will not work if the call-site in user code is nested inside a generic type or method.Right now, this call to
FromFunctionPointer
would cause aNotImplementedException
to be thrown at runtime.Proposed solution
This scenario is already detected during codegen, but the fix is not yet implemented. We cannot intercept this using closed type symbols discovered from the
GenericSymbolReferenceTree
, because the signatures of the closed types don't match the signature of the open type at the call-site.Instead, the only approach we can take here is to perform runtime type-checking and dispatch. We do already know the possible types used in this compilation, thanks to the tree. Marshalling behaviors must be parsable at compile-time. Calling conventions that are not parsable at compile-time currently default to
CallingConvention.Winapi
(the system default calling convention). Ergo, we do have all necessary details to dispatch these calls.When building the
ClassDescriptorCollection
, we should check whether the call-site falls into this category. If it does, then we should create a separate collection so we know whatclass
es to generated, and omit producing the interceptor. This separate collection will need to access the generatedclass
name (which may be shared with other call-sites). This collection can use normal equality comparison for theMethodReference
(rather than the equality comparer used in building theClassDescriptorCollection
).Finally, after the codegen for the
class
es has been completed, we should enumerate this new collection and produce a single interceptor that performs the runtime type-checking.Additional considerations
This should be considered as a primary use-case. This issue should be treated as high priority.