Repository: dotnet/runtime (2026-04-09T16:51:52Z)

Issue:
ComInterfaceGenerator produces invalid C# for `partial` interface methods.
Looks like we just copy the partial modifier from user code when we shouldn't

Code:
```C#
[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(IID)]
internal partial interface ICalculator
{
    int Add(int a, int b);
    public partial int Subtract(int a, int b) => 0;
    public partial int Subtract(int a, int b);
    public const string IID = "c67121c6-cf26-431f-adc7-d12fe2448841";
}
```
generated:

```C#
// <auto-generated />
#pragma warning disable CS0612, CS0618
file unsafe class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType
{
    public static global::System.Guid Iid { get; } = new(new global::System.ReadOnlySpan<byte>(new byte[] { 198, 33, 113, 198, 38, 207, 31, 67, 173, 199, 209, 47, 226, 68, 136, 65 }));

    private static void** _vtable;
    public static void** ManagedVirtualMethodTable => _vtable != null ? _vtable : (_vtable = InterfaceImplementation.CreateManagedVirtualFunctionTable());
}

[global::System.Runtime.InteropServices.DynamicInterfaceCastableImplementationAttribute]
file unsafe partial interface InterfaceImplementation : global::Tutorial.ICalculator
{
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.ComInterfaceGenerator", "8.0.8.41904")]
    [global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
    int global::Tutorial.ICalculator.Add(int a, int b)
    {
        var(__this, __vtable_native) = ((global::System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(typeof(global::Tutorial.ICalculator));
        int __retVal;
        int __invokeRetVal;
        {
            __invokeRetVal = ((delegate* unmanaged[MemberFunction]<void*, int, int, int*, int> )__vtable_native[3])(__this, a, b, &__retVal);
        }

        // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
        global::System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(__invokeRetVal);
        global::System.GC.KeepAlive(this);
        return __retVal;
    }

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.ComInterfaceGenerator", "8.0.8.41904")]
    [global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
    partial int global::Tutorial.ICalculator.Subtract(int a, int b)
    {
        var(__this, __vtable_native) = ((global::System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(typeof(global::Tutorial.ICalculator));
        int __retVal;
        int __invokeRetVal;
        {
            __invokeRetVal = ((delegate* unmanaged[MemberFunction]<void*, int, int, int*, int> )__vtable_native[4])(__this, a, b, &__retVal);
        }

        // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
        global::System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(__invokeRetVal);
        global::System.GC.KeepAlive(this);
        return __retVal;
    }
}

file unsafe partial interface InterfaceImplementation
{
    [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(CallConvs = new[] { typeof(global::System.Runtime.CompilerServices.CallConvMemberFunction) })]
    internal static int ABI_Add(global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch* __this_native, int a, int b, int* __invokeRetValUnmanaged__param)
    {
        global::Tutorial.ICalculator @this = default;
        ref int __invokeRetValUnmanaged = ref *__invokeRetValUnmanaged__param;
        int __invokeRetVal = default;
        int __retVal = default;
        try
        {
            // Unmarshal - Convert native data to managed data.
            @this = global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch.GetInstance<global::Tutorial.ICalculator>(__this_native);
            __invokeRetVal = @this.Add(a, b);
            // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
            __retVal = 0; // S_OK
            // Marshal - Convert managed data to native data.
            __invokeRetValUnmanaged = __invokeRetVal;
        }
        catch (global::System.Exception __exception)
        {
            __retVal = global::System.Runtime.InteropServices.Marshalling.ExceptionAsHResultMarshaller<int>.ConvertToUnmanaged(__exception);
        }

        return __retVal;
    }

    [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(CallConvs = new[] { typeof(global::System.Runtime.CompilerServices.CallConvMemberFunction) })]
    internal static int ABI_Subtract(global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch* __this_native, int a, int b, int* __invokeRetValUnmanaged__param)
    {
        global::Tutorial.ICalculator @this = default;
        ref int __invokeRetValUnmanaged = ref *__invokeRetValUnmanaged__param;
        int __invokeRetVal = default;
        int __retVal = default;
        try
        {
            // Unmarshal - Convert native data to managed data.
            @this = global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch.GetInstance<global::Tutorial.ICalculator>(__this_native);
            __invokeRetVal = @this.Subtract(a, b);
            // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
            __retVal = 0; // S_OK
            // Marshal - Convert managed data to native data.
            __invokeRetValUnmanaged = __invokeRetVal;
        }
        catch (global::System.Exception __exception)
        {
            __retVal = global::System.Runtime.InteropServices.Marshalling.ExceptionAsHResultMarshaller<int>.ConvertToUnmanaged(__exception);
        }

        return __retVal;
    }
}

file unsafe partial interface InterfaceImplementation
{
    internal static void** CreateManagedVirtualFunctionTable()
    {
        void** vtable = (void**)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(global::Tutorial.ICalculator), sizeof(void*) * 5);
        {
            nint v0, v1, v2;
            global::System.Runtime.InteropServices.ComWrappers.GetIUnknownImpl(out v0, out v1, out v2);
            vtable[0] = (void*)v0;
            vtable[1] = (void*)v1;
            vtable[2] = (void*)v2;
        }

        {
            vtable[3] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, int, int, int*, int> )&ABI_Add;
            vtable[4] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, int, int, int*, int> )&ABI_Subtract;
        }

        return vtable;
    }
}

namespace Tutorial
{
    [global::System.Runtime.InteropServices.Marshalling.IUnknownDerivedAttribute<InterfaceInformation, InterfaceImplementation>]
    internal partial interface ICalculator
    {
    }
}

namespace Tutorial
{
    internal partial interface ICalculator
    {
    }
}
```
Error:
```
Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS0759: No defining declaration found for implementing declaration of partial method 'InterfaceImplementation.Subtract(int, int)'

Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS0754: A partial method may not explicitly implement an interface method

Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS0539: 'InterfaceImplementation.Subtract(int, int)' in explicit interface declaration is not found among members of the interface that can be implemented

Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS8796: Partial method 'InterfaceImplementation.Subtract(int, int)' must have accessibility modifiers because it has a non-void return type. 
```


Discussion:
Tagging subscribers to this area: @dotnet/interop-contrib
See info in [area-owners.md](https://github.com/dotnet/runtime/blob/main/docs/area-owners.md) if you want to be subscribed.
<details>
<summary>Issue Details</summary>
<hr />

Looks like we just copy the partial modifier from user code when we shouldn't

Code:
```C#
[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(IID)]
internal partial interface ICalculator
{
    int Add(int a, int b);
    public partial int Subtract(int a, int b) => 0;
    public partial int Subtract(int a, int b);
    public const string IID = "c67121c6-cf26-431f-adc7-d12fe2448841";
}
```
generated:

```C#
// <auto-generated />
#pragma warning disable CS0612, CS0618
file unsafe class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType
{
    public static global::System.Guid Iid { get; } = new(new global::System.ReadOnlySpan<byte>(new byte[] { 198, 33, 113, 198, 38, 207, 31, 67, 173, 199, 209, 47, 226, 68, 136, 65 }));

    private static void** _vtable;
    public static void** ManagedVirtualMethodTable => _vtable != null ? _vtable : (_vtable = InterfaceImplementation.CreateManagedVirtualFunctionTable());
}

[global::System.Runtime.InteropServices.DynamicInterfaceCastableImplementationAttribute]
file unsafe partial interface InterfaceImplementation : global::Tutorial.ICalculator
{
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.ComInterfaceGenerator", "8.0.8.41904")]
    [global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
    int global::Tutorial.ICalculator.Add(int a, int b)
    {
        var(__this, __vtable_native) = ((global::System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(typeof(global::Tutorial.ICalculator));
        int __retVal;
        int __invokeRetVal;
        {
            __invokeRetVal = ((delegate* unmanaged[MemberFunction]<void*, int, int, int*, int> )__vtable_native[3])(__this, a, b, &__retVal);
        }

        // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
        global::System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(__invokeRetVal);
        global::System.GC.KeepAlive(this);
        return __retVal;
    }

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.ComInterfaceGenerator", "8.0.8.41904")]
    [global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
    partial int global::Tutorial.ICalculator.Subtract(int a, int b)
    {
        var(__this, __vtable_native) = ((global::System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(typeof(global::Tutorial.ICalculator));
        int __retVal;
        int __invokeRetVal;
        {
            __invokeRetVal = ((delegate* unmanaged[MemberFunction]<void*, int, int, int*, int> )__vtable_native[4])(__this, a, b, &__retVal);
        }

        // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
        global::System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(__invokeRetVal);
        global::System.GC.KeepAlive(this);
        return __retVal;
    }
}

file unsafe partial interface InterfaceImplementation
{
    [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(CallConvs = new[] { typeof(global::System.Runtime.CompilerServices.CallConvMemberFunction) })]
    internal static int ABI_Add(global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch* __this_native, int a, int b, int* __invokeRetValUnmanaged__param)
    {
        global::Tutorial.ICalculator @this = default;
        ref int __invokeRetValUnmanaged = ref *__invokeRetValUnmanaged__param;
        int __invokeRetVal = default;
        int __retVal = default;
        try
        {
            // Unmarshal - Convert native data to managed data.
            @this = global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch.GetInstance<global::Tutorial.ICalculator>(__this_native);
            __invokeRetVal = @this.Add(a, b);
            // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
            __retVal = 0; // S_OK
            // Marshal - Convert managed data to native data.
            __invokeRetValUnmanaged = __invokeRetVal;
        }
        catch (global::System.Exception __exception)
        {
            __retVal = global::System.Runtime.InteropServices.Marshalling.ExceptionAsHResultMarshaller<int>.ConvertToUnmanaged(__exception);
        }

        return __retVal;
    }

    [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(CallConvs = new[] { typeof(global::System.Runtime.CompilerServices.CallConvMemberFunction) })]
    internal static int ABI_Subtract(global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch* __this_native, int a, int b, int* __invokeRetValUnmanaged__param)
    {
        global::Tutorial.ICalculator @this = default;
        ref int __invokeRetValUnmanaged = ref *__invokeRetValUnmanaged__param;
        int __invokeRetVal = default;
        int __retVal = default;
        try
        {
            // Unmarshal - Convert native data to managed data.
            @this = global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch.GetInstance<global::Tutorial.ICalculator>(__this_native);
            __invokeRetVal = @this.Subtract(a, b);
            // NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
            __retVal = 0; // S_OK
            // Marshal - Convert managed data to native data.
            __invokeRetValUnmanaged = __invokeRetVal;
        }
        catch (global::System.Exception __exception)
        {
            __retVal = global::System.Runtime.InteropServices.Marshalling.ExceptionAsHResultMarshaller<int>.ConvertToUnmanaged(__exception);
        }

        return __retVal;
    }
}

file unsafe partial interface InterfaceImplementation
{
    internal static void** CreateManagedVirtualFunctionTable()
    {
        void** vtable = (void**)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(global::Tutorial.ICalculator), sizeof(void*) * 5);
        {
            nint v0, v1, v2;
            global::System.Runtime.InteropServices.ComWrappers.GetIUnknownImpl(out v0, out v1, out v2);
            vtable[0] = (void*)v0;
            vtable[1] = (void*)v1;
            vtable[2] = (void*)v2;
        }

        {
            vtable[3] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, int, int, int*, int> )&ABI_Add;
            vtable[4] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, int, int, int*, int> )&ABI_Subtract;
        }

        return vtable;
    }
}

namespace Tutorial
{
    [global::System.Runtime.InteropServices.Marshalling.IUnknownDerivedAttribute<InterfaceInformation, InterfaceImplementation>]
    internal partial interface ICalculator
    {
    }
}

namespace Tutorial
{
    internal partial interface ICalculator
    {
    }
}
```
Error:
```
Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS0759: No defining declaration found for implementing declaration of partial method 'InterfaceImplementation.Subtract(int, int)'

Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS0754: A partial method may not explicitly implement an interface method

Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS0539: 'InterfaceImplementation.Subtract(int, int)' in explicit interface declaration is not found among members of the interface that can be implemented

Microsoft.Interop.ComInterfaceGenerator\ICalculator.cs(33,46): error CS8796: Partial method 'InterfaceImplementation.Subtract(int, int)' must have accessibility modifiers because it has a non-void return type. 
```

<table>
  <tr>
    <th align="left">Author:</th>
    <td>jtschuster</td>
  </tr>
  <tr>
    <th align="left">Assignees:</th>
    <td>-</td>
  </tr>
  <tr>
    <th align="left">Labels:</th>
    <td>

`area-System.Runtime.InteropServices`, `source-generator`

</td>
  </tr>
  <tr>
    <th align="left">Milestone:</th>
    <td>9.0.0</td>
  </tr>
</table>
</details>
Tagging subscribers to this area: @dotnet/interop-contrib
See info in [area-owners.md](https://github.com/dotnet/runtime/blob/main/docs/area-owners.md) if you want to be subscribed.
<!-- Policy app identification https://img.shields.io/static/v1?label=PullRequestIssueManagement. -->



Patch:
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs
index dad35781688f1a..3b110111667f92 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs
@@ -363,7 +363,7 @@ internal static IncrementalMethodStubGenerationContext CalculateStubInformation(
 
             var containingSyntaxContext = new ContainingSyntaxContext(syntax);
             var methodSyntaxTemplate = new ContainingSyntax(
-                new SyntaxTokenList(syntax.Modifiers.Where(static m => !m.IsKind(SyntaxKind.NewKeyword))).StripAccessibilityModifiers(),
+                new SyntaxTokenList(syntax.Modifiers.Where(static m => !m.IsKind(SyntaxKind.NewKeyword) && !m.IsKind(SyntaxKind.PartialKeyword) && !m.IsKind(SyntaxKind.VirtualKeyword))).StripAccessibilityModifiers(),
                 SyntaxKind.MethodDeclaration,
                 syntax.Identifier,
                 syntax.TypeParameterList);
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs
index 4db8815cb94a8e..c6e61bc8166386 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs
@@ -268,7 +268,7 @@ internal static SourceAvailableIncrementalMethodStubGenerationContext CalculateS
 
             var containingSyntaxContext = new ContainingSyntaxContext(syntax);
 
-            var methodSyntaxTemplate = new ContainingSyntax(syntax.Modifiers.StripAccessibilityModifiers(), SyntaxKind.MethodDeclaration, syntax.Identifier, syntax.TypeParameterList);
+            var methodSyntaxTemplate = new ContainingSyntax(new SyntaxTokenList(syntax.Modifiers.Where(static m => !m.IsKind(SyntaxKind.PartialKeyword) && !m.IsKind(SyntaxKind.VirtualKeyword))).StripAccessibilityModifiers(), SyntaxKind.MethodDeclaration, syntax.Identifier, syntax.TypeParameterList);
 
             ImmutableArray<FunctionPointerUnmanagedCallingConventionSyntax> callConv = VirtualMethodPointerStubGenerator.GenerateCallConvSyntaxFromAttributes(suppressGCTransitionAttribute, unmanagedCallConvAttribute, defaultCallingConventions: ImmutableArray<FunctionPointerUnmanagedCallingConventionSyntax>.Empty);
 


Test patch:
diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs
index c0806b2853d8f5..7b853db968adb1 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs
+++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs
@@ -376,6 +376,29 @@ public async Task ValidateComInterfaceSnippets(string id, string source)
             await VerifyComInterfaceGenerator.VerifySourceGeneratorAsync(source);
         }
 
+        [Fact]
+        public async Task PartialMethodModifierOnComInterfaceMethodCompiles()
+        {
+            string source = """
+                using System.Runtime.InteropServices;
+                using System.Runtime.InteropServices.Marshalling;
+
+                [GeneratedComInterface]
+                [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")]
+                internal partial interface IComInterface
+                {
+                    void Method();
+                    public virtual partial void PartialMethod();
+                }
+                internal partial interface IComInterface
+                {
+                    public virtual partial void PartialMethod() { }
+                }
+                """;
+
+            await VerifyComInterfaceGenerator.VerifySourceGeneratorAsync(source);
+        }
+
         [Fact]
         public async Task DocumentedComInterfaceDoesNotProduceCS1591Warnings()
         {