Skip to content

[Interop] AddGeneratedComClassFixer: remove [ComVisible(true)] during conversion#126105

Open
Copilot wants to merge 4 commits intomainfrom
copilot/fix-comvisible-attribute-removal
Open

[Interop] AddGeneratedComClassFixer: remove [ComVisible(true)] during conversion#126105
Copilot wants to merge 4 commits intomainfrom
copilot/fix-comvisible-attribute-removal

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

The AddGeneratedComClassFixer code fix was not removing [ComVisible(true)] when converting a class to use [GeneratedComClass], leaving a redundant legacy attribute alongside the new one. This includes cases where [ComVisible(true)] appears on a different partial declaration in a separate file.

Description

  • ConvertToSourceGeneratedInteropFixer.cs: Changed the fix delegate type from Func<DocumentEditor, CancellationToken, Task> to Func<SolutionEditor, DocumentId, CancellationToken, Task> throughout (base class, RegisterCodeFixesAsync, ApplyActionAndEnableUnsafe, and CustomFixAllProvider). This enables fixers to edit multiple documents in a single code action, which is necessary for cross-document attribute removal.
  • AddGeneratedComClassFixer.cs: Rewrote AddGeneratedComClassAsync to use SolutionEditor. Now obtains the INamedTypeSymbol before applying edits, removes all [ComVisible(true)] attributes (not just the first) across any partial declarations, and uses solutionEditor.OriginalSolution.GetDocumentId(syntaxRef.SyntaxTree) to find which document each attribute lives in — safely handling cross-document, same-document, and metadata (null) cases.
  • ConvertComImportToGeneratedComInterfaceFixer.cs and ConvertToLibraryImportFixer.cs: Updated CreateFixForSelectedOptions to use the new solution-level signature; each implementation obtains its DocumentEditor from the SolutionEditor internally.
  • AddGeneratedComClassTests.cs: Added four tests:
    • TypeWithComVisibleTrue_RemovesComVisibleAttribute[ComVisible(true)] is removed (same declaration)
    • TypeWithComVisibleFalse_PreservesComVisibleAttribute[ComVisible(false)] is preserved (different semantics)
    • TypeWithComVisibleTrueOnSecondPartialDeclaration_RemovesComVisibleAttribute[ComVisible(true)] on a second partial declaration in the same file is removed
    • TypeWithComVisibleTrueInSeparateFile_RemovesComVisibleAttribute[ComVisible(true)] on a partial declaration in a separate file is removed correctly

Before fix, applying the codefix to:

[ComVisible(true)]
class C : I { }

would produce [ComVisible(true)][GeneratedComClass] partial class C : I. After, it correctly produces [GeneratedComClass] partial class C : I — even when [ComVisible(true)] is on a different partial declaration in another file.

Customer Impact

Users applying the "convert to GeneratedComClass" codefix are left with a stale [ComVisible(true)] attribute that has no effect in the new model and causes unnecessary noise/confusion. This applies whether the attribute is on the same declaration or any other partial declaration of the class, including those in separate files.

Regression

No — this is a missing feature in the codefix, not a regression.

Testing

8 unit tests in ComInterfaceGenerator.Unit.Tests (including the 4 new ones), all passing. Full suite of 852 tests passes. LibraryImportGenerator unit tests (95 tests) also pass, confirming no regressions in the refactored base class.

Risk

Low. Change is isolated to the Roslyn analyzer/fixer; only affects the code transformation applied by the IDE codefix. Runtime behavior is unchanged. The base class refactoring to SolutionEditor is a pure internal restructuring — all existing fixer behavior is preserved.

Original prompt

This section details on the original issue you should resolve

<issue_title>GeneratedComClass codefix doesn't remove [ComVisible(true)] from classes</issue_title>
<issue_description>If [ComVisible(true)] is present on a class converted to a GeneratedComClass, the codefixer should remove the attribute.</issue_description>

Comments on the Issue (you are @copilot in this section)

@ 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.
Issue Details

If [ComVisible(true)] is present on a class converted to a GeneratedComClass, the codefixer should remove the attribute.

Author: jtschuster
Assignees: -
Labels:

area-System.Runtime.InteropServices, code-analyzer

Milestone: 9.0.0

💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

…rting to GeneratedComClass

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9868b554-ef51-450a-8249-c7b0769c456a
Copilot AI changed the title [WIP] Fix codefix to remove ComVisible attribute from GeneratedComClass [Interop] AddGeneratedComClassFixer: remove [ComVisible(true)] during conversion Mar 25, 2026
Copilot AI requested a review from jkoritzinsky March 25, 2026 17:31
@jkoritzinsky
Copy link
Copy Markdown
Member

@copilot please add another test where the class we will apply the fixer to has two partial definitions and the [ComVisible(true)] attribute is on the second one.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 25, 2026

@copilot please add another test where the class we will apply the fixer to has two partial definitions and the [ComVisible(true)] attribute is on the second one.

Added in b2e5714. The test TypeWithComVisibleTrueOnSecondPartialDeclaration_RemovesComVisibleAttribute has the first partial declaration (without [ComVisible(true)]) triggering the diagnostic, while the second partial declaration carries [ComVisible(true)]. The fixer correctly removes it from the second partial since GetDeclaredSymbol returns the full INamedTypeSymbol with attributes from all partial declarations.

@jkoritzinsky jkoritzinsky marked this pull request as ready for review March 25, 2026 18:38
Copilot AI review requested due to automatic review settings March 25, 2026 18:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the ComInterfaceGenerator Roslyn code fix so converting a class to [GeneratedComClass] also removes redundant legacy [ComVisible(true)] attributes, reducing noise and aligning the produced code with the source-generated COM model.

Changes:

  • Updated AddGeneratedComClassFixer to find and remove [ComVisible(true)] during the conversion.
  • Made the fixer path async and added semantic checks using SymbolEqualityComparer.Default.
  • Added unit tests validating removal/preservation behavior (including a same-file second partial declaration case).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Analyzers/AddGeneratedComClassFixer.cs Removes [ComVisible(true)] while applying [GeneratedComClass] and partial.
src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/AddGeneratedComClassTests.cs Adds tests for [ComVisible(true)] removal, [ComVisible(false)] preservation, and partial declarations.

if (comVisibleAttr?.ApplicationSyntaxReference is { } syntaxRef)
{
var comVisibleAttrSyntax = await syntaxRef.GetSyntaxAsync(ct).ConfigureAwait(false);
editor.RemoveNode(comVisibleAttrSyntax);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comVisibleAttr.ApplicationSyntaxReference can point to an attribute on a different partial declaration in another document. In that case syntaxRef.GetSyntaxAsync(ct) returns a node from a different SyntaxTree, and DocumentEditor.RemoveNode will fail because the node is not part of the current document being edited. Please guard against cross-document syntax references (e.g., only remove when syntaxRef.SyntaxTree matches the current document) or rework the code action to edit the containing document/solution as needed.

Suggested change
editor.RemoveNode(comVisibleAttrSyntax);
if (comVisibleAttrSyntax.SyntaxTree == editor.OriginalRoot.SyntaxTree)
{
editor.RemoveNode(comVisibleAttrSyntax);
}

Copilot uses AI. Check for mistakes.
@jkoritzinsky
Copy link
Copy Markdown
Member

@copilot please address the feedback from @copilot by adjusting the code fixer here to be a SolutionEditor and editing across documents when needed.

…rue) removal

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b1015873-0d8e-46e6-8159-57fa074fc344

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 31, 2026

@copilot please address the feedback from @copilot by adjusting the code fixer here to be a SolutionEditor and editing across documents when needed.

Done in eeafcc6. The fix now:

  1. Changed ConvertToSourceGeneratedInteropFixer to use Func<SolutionEditor, DocumentId, CancellationToken, Task> throughout (base class, fix-all provider, and RegisterCodeFixesAsync) so all fixers now operate at the solution level.
  2. AddGeneratedComClassFixer uses solutionEditor.OriginalSolution.GetDocumentId(syntaxRef.SyntaxTree) to find which document each [ComVisible(true)] attribute lives in, then calls solutionEditor.GetDocumentEditorAsync(attrDocumentId, ct) to get the right editor — handling cross-document and same-document cases uniformly.
  3. All [ComVisible(true)] attributes are now removed (not just the first) via .Where(...).ToArray() + a foreach loop.
  4. Added a cross-document test TypeWithComVisibleTrueInSeparateFile_RemovesComVisibleAttribute where the attribute is in a separate source file.
  5. ConvertComImportToGeneratedComInterfaceFixer and ConvertToLibraryImportFixer updated to use the new signature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status
Status: No status

Development

Successfully merging this pull request may close these issues.

GeneratedComClass codefix doesn't remove [ComVisible(true)] from classes

3 participants