Files
Misaki.HighPerformance/Misaki.HighPerformance.Analyzer/Misaki.HighPerformance.Analyzer.CodeFixes/StructCopyCodeFixProvider.cs
Misaki 27dfa67784
Some checks failed
Publish NuGet Packages / publish (pull_request) Has been cancelled
Add Roslyn analyzer and code fix for unique ownership
Introduce a Roslyn analyzer to enforce unique ownership semantics for structs marked with the `[NonCopyable]` attribute. Added a corresponding code fix to resolve violations by suggesting the use of `Share()` or other ownership transfer methods.

Key changes:
- Added `StructCopyCodeAnalyzer` to detect invalid struct copies.
- Implemented `StructCopyCodeFixProvider` to provide code fixes.
- Created `Misaki.HighPerformance.Analyzer` and `CodeFixes` projects.
- Added unit tests for the analyzer and code fixes.
- Introduced `UniquePtr<T>` and `SharedPtr<T>` for pointer ownership.
- Added a Visual Studio extension project and packaging support.
- Updated `UnsafeUtility` to use `nint`/`nuint` for indices.
2025-11-22 18:20:03 +09:00

92 lines
4.1 KiB
C#

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Misaki.HighPerformance.Analyzer
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StructCopyCodeFixProvider)), Shared]
public class StructCopyCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get
{
return ImmutableArray.Create(StructCopyCodeAnalyzer.DIAGNOSTIC_ID);
}
}
public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
// Find the expression identified by the diagnostic.
// This will be the Right-Hand Side (RHS) of the assignment or declaration
var expressionSyntax = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf()
.OfType<ExpressionSyntax>()
.First();
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: "Share",
createChangedDocument: c => TransferOwnershipAsync(context.Document, expressionSyntax, c),
equivalenceKey: nameof(StructCopyCodeFixProvider)),
diagnostic);
}
private async Task<Document> TransferOwnershipAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken)
{
// 1. Get the semantic model to figure out exactly what type we are dealing with
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var typeSymbol = semanticModel.GetTypeInfo(expressionToFix).Type;
// 2. Generate the type name string (e.g., "UniquePtr<ID3D12Device>")
// We use ToMinimalDisplayString so Roslyn handles namespaces/using directives automatically.
var typeName = typeSymbol.ToMinimalDisplayString(semanticModel, expressionToFix.SpanStart);
var typeSyntax = SyntaxFactory.ParseTypeName(typeName);
// 3. Create the "Share()" invocation: expression.Share()
var detachMethod = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
expressionToFix, // The 'a' in 'b = a'
SyntaxFactory.IdentifierName("Share")
);
var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod);
// 4. Create the "new UniquePtr<T>(...)" expression
var newObjectCreation = SyntaxFactory.ObjectCreationExpression(typeSyntax)
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Argument(detachInvocation)
)
)
)
.WithAdditionalAnnotations(Formatter.Annotation); // Auto-format whitespace
// 5. Replace the old node with the new node in the Syntax Tree
var root = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = root.ReplaceNode(expressionToFix, newObjectCreation);
return document.WithSyntaxRoot(newRoot);
}
}
}