File: Microsoft.NetCore.Analyzers\Performance\PreferReadOnlySpanOverSpanTests.cs
Web Access
Project: ..\..\..\src\Microsoft.CodeAnalysis.NetAnalyzers\tests\Microsoft.CodeAnalysis.NetAnalyzers.UnitTests\Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj (Microsoft.CodeAnalysis.NetAnalyzers.UnitTests)
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the MIT license.  See License.txt in the project root for license information.
 
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Testing;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
    Microsoft.NetCore.Analyzers.Performance.PreferReadOnlySpanOverSpanAnalyzer,
    Microsoft.NetCore.Analyzers.Performance.PreferReadOnlySpanOverSpanFixer>;
 
namespace Microsoft.NetCore.Analyzers.Performance.UnitTests
{
    public class PreferReadOnlySpanOverSpanTests
    {
        [Fact]
        public async Task SpanParameter_NotWritten_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        var length = data.Length;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ReassignedToOwnSliceLoop_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        while (!data.IsEmpty)
                        {
                            int idx = data.IndexOf((byte)0);
                            if (idx < 0)
                                break;
                            data = data.Slice(idx + 1);
                        }
                        _ = data.Length;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        while (!data.IsEmpty)
                        {
                            int idx = data.IndexOf((byte)0);
                            if (idx < 0)
                                break;
                            data = data.Slice(idx + 1);
                        }
                        _ = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ReassignedToOwnSliceLoopThenConsumedByWritableApi_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private static void Consume(Span<byte> span) { }
 
                    private void M(Span<byte> data)
                    {
                        while (!data.IsEmpty)
                        {
                            int idx = data.IndexOf((byte)1);
                            if (idx < 0)
                                break;
                            data = data.Slice(idx + 1);
                        }
                        // Passed to writable Span API makes parameter unsafe for conversion
                        Consume(data);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ReassignedToOwnSliceLoopThenWritten_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        while (!data.IsEmpty)
                        {
                            int idx = data.IndexOf((byte)2);
                            if (idx < 0)
                                break;
                            data = data.Slice(idx + 1);
                        }
                        // Write to the parameter after slicing keeps it writable-only
                        if (!data.IsEmpty)
                        {
                            data[0] = 42;
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedToMethodReadOnly_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        Helper(data);
                    }
 
                    private void Helper(ReadOnlySpan<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        Helper(data);
                    }
 
                    private void Helper(ReadOnlySpan<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInForEachLoop_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        foreach (var b in data)
                        {
                            Console.WriteLine(b);
                        }
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        foreach (var b in data)
                        {
                            Console.WriteLine(b);
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInForLoop_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            byte b = data[i];
                            Console.WriteLine(b);
                        }
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            byte b = data[i];
                            Console.WriteLine(b);
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInLinqQuery_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
                using System.Linq;
 
                class C
                {
                    private void M(Span<int> [|data|])
                    {
                        var sum = data.ToArray().Sum();
                    }
                }
                """, """
                using System;
                using System.Linq;
 
                class C
                {
                    private void M(ReadOnlySpan<int> data)
                    {
                        var sum = data.ToArray().Sum();
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ReadThroughIndexer_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        var first = data[0];
                        var last = data[data.Length - 1];
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        var first = data[0];
                        var last = data[data.Length - 1];
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_InTernaryExpression_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|], bool condition)
                    {
                        var length = condition ? data.Length : 0;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data, bool condition)
                    {
                        var length = condition ? data.Length : 0;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedToGenericMethod_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        Helper<byte>(data);
                    }
 
                    private void Helper<T>(ReadOnlySpan<T> data)
                    {
                        Console.WriteLine(data.Length);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        Helper<byte>(data);
                    }
 
                    private void Helper<T>(ReadOnlySpan<T> data)
                    {
                        Console.WriteLine(data.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInReturnStatement_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private ReadOnlySpan<byte> M(Span<byte> [|data|])
                    {
                        return data;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private ReadOnlySpan<byte> M(ReadOnlySpan<byte> data)
                    {
                        return data;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ConditionalAccess_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        var result = data.IsEmpty ? 0 : data[0];
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        var result = data.IsEmpty ? 0 : data[0];
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_MultipleReferences_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        Console.WriteLine(data.Length);
                        Console.WriteLine(data[0]);
                        Console.WriteLine(data[1]);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        Console.WriteLine(data.Length);
                        Console.WriteLine(data[0]);
                        Console.WriteLine(data[1]);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_CopyTo_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        Span<byte> destination = stackalloc byte[10];
                        data.CopyTo(destination);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        Span<byte> destination = stackalloc byte[10];
                        data.CopyTo(destination);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_TryCopyTo_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        Span<byte> destination = stackalloc byte[10];
                        data.TryCopyTo(destination);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        Span<byte> destination = stackalloc byte[10];
                        data.TryCopyTo(destination);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ReturnedAsReadOnlySpan_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private ReadOnlySpan<byte> M(Span<byte> [|data|])
                    {
                        return data; // Returning as readonly
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private ReadOnlySpan<byte> M(ReadOnlySpan<byte> data)
                    {
                        return data; // Returning as readonly
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_StoredInReadOnlyMemoryField_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private ReadOnlyMemory<byte> _field;
 
                    private void M(Memory<byte> [|data|])
                    {
                        _field = data;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private ReadOnlyMemory<byte> _field;
 
                    private void M(ReadOnlyMemory<byte> data)
                    {
                        _field = data;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_StoredInReadOnlyMemoryArray_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<int> [|data|])
                    {
                        var array = new ReadOnlyMemory<int>[] { data };
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlyMemory<int> data)
                    {
                        var array = new ReadOnlyMemory<int>[] { data };
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_StoredInReadOnlySpanProperty_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                ref struct Container
                {
                    public ReadOnlySpan<byte> Data { get; set; }
                }
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        var container = new Container { Data = data };
                    }
                }
                """, """
                using System;
 
                ref struct Container
                {
                    public ReadOnlySpan<byte> Data { get; set; }
                }
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        var container = new Container { Data = data };
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_PassedToMethodExpectingReadOnly_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<int> [|data|])
                    {
                        Helper(data);
                    }
 
                    private void Helper(ReadOnlyMemory<int> data)
                    {
                        Console.WriteLine(data.Length);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlyMemory<int> data)
                    {
                        Helper(data);
                    }
 
                    private void Helper(ReadOnlyMemory<int> data)
                    {
                        Console.WriteLine(data.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_NotWritten_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<int> data)
                    {
                        var span = data.Span; // .Span stores result in local - can't verify safe usage
                        var length = span.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_Written_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        data[0] = 1;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_WrittenViaIndexer_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<int> data)
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            data[i] = i;
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedAsRefParameter_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        Helper(ref data);
                    }
 
                    private void Helper(ref Span<byte> data)
                    {
                        data[0] = 1;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task PublicMethod_DefaultConfig_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                public class C
                {
                    public void M(Span<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task OverrideMethod_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                public class Base
                {
                    public virtual void M(Span<byte> data) { }
                }
 
                public class Derived : Base
                {
                    public override void M(Span<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task InterfaceImplementation_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                public interface I
                {
                    void M(Span<byte> data);
                }
 
                public class C : I
                {
                    public void M(Span<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task ReadOnlySpanParameter_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        var length = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_SlicedButNotWritten_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        var slice = data.Slice(0, 10);
                        var length = slice.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MultipleParameters_MixedUsage()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|readOnlyData|], Span<byte> writableData)
                    {
                        var length = readOnlyData.Length;
                        writableData[0] = 1;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> readOnlyData, Span<byte> writableData)
                    {
                        var length = readOnlyData.Length;
                        writableData[0] = 1;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedToWritableSpanMethod_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        Helper(data);
                    }
 
                    private void Helper(Span<byte> data)
                    {
                        data[0] = 1;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_CopiedToLocal_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        var copy = data;
                        Console.WriteLine(copy.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedAsOutArgument_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        Helper(out data);
                    }
 
                    private void Helper(out Span<byte> data)
                    {
                        data = default;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_AccessSpanProperty_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<int> data)
                    {
                        var s = data.Span; // .Span stores result in local - can't verify safe usage
                        Console.WriteLine(s[0]);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_SliceAndRead_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<int> data)
                    {
                        var slice = data.Slice(1, 5); // Slice stored in local - can't verify safe usage
                        Console.WriteLine(slice.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_RangeOperator_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        var slice = data[1..5];
                        Console.WriteLine(slice.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_RangeFromEndOperator_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        var slice = data[^3..^1];
                        Console.WriteLine(slice.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ReturnedFromMethod_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private Span<byte> M(Span<byte> data)
                    {
                        return data;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ExpressionReturnedFromMethod_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private Span<byte> M(Span<byte> data) => data;
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_StoredInRefParameter_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                
                using System;
 
                class C
                {
                    private void M(Span<byte> data, ref Span<byte> output)
                    {
                        output = data;
                    }
                }
                
                """);
        }
 
        [Fact]
        public async Task SpanParameter_StoredInOutParameter_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data, out Span<byte> output)
                    {
                        output = data;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_StoredInField_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private Memory<byte> _field;
 
                    private void M(Memory<byte> data)
                    {
                        _field = data;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_StoredInArray_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<int> data)
                    {
                        var array = new Memory<int>[] { data };
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_StoredInProperty_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                ref struct Container
                {
                    public Span<byte> Data { get; set; }
                }
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        var container = new Container { Data = data };
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_MultipleReferencesOneWrite_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        Console.WriteLine(data.Length);
                        Console.WriteLine(data[0]);
                        data[1] = 5; // Write
                        Console.WriteLine(data[2]);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedAsRefArgument_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class Test
                {
                    private static void IntroSort(Span<int> keys, int depthLimit)
                    {
                        if (keys.Length == 2)
                        {
                            SwapIfGreater(ref keys[0], ref keys[1]);
                            return;
                        }
                    }
 
                    private static void SwapIfGreater(ref int a, ref int b)
                    {
                        if (a > b)
                        {
                            int temp = a;
                            a = b;
                            b = temp;
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_RefVariableDeclaration_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class Test
                {
                    private void Method(Span<int> data)
                    {
                        // Taking a ref to an indexed element requires writability
                        ref int firstElement = ref data[0];
                        firstElement = 42;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_RefReturn_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class Test
                {
                    // Method returns ref, so parameter must be writable
                    private ref int GetFirst(Span<int> data)
                    {
                        return ref data[0];
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_PassedToMethodViaSlice_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
                using System.Threading;
                using System.Threading.Tasks;
 
                class Test
                {
                    // buffer is passed via Slice to a method that expects writable Memory<char>
                    internal async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
                    {
                        int n = 0, i;
                        do
                        {
                            i = await ReadAsyncInternal(buffer.Slice(n), cancellationToken).ConfigureAwait(false);
                            n += i;
                        } while (i > 0 && n < buffer.Length);
 
                        return n;
                    }
 
                    private ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
                    {
                        // Implementation that writes to buffer
                        if (buffer.Length > 0)
                            buffer.Span[0] = 'x';
                        return ValueTask.FromResult(buffer.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInFixed_NoDiagnostic()
        {
            var source = """
                using System;
 
                class Test
                {
                    private unsafe void ProcessData(Span<byte> data)
                    {
                        fixed (byte* ptr = data)
                        {
                            // Use pointer
                            *ptr = 42;
                        }
                    }
                }
                """;
 
            await new VerifyCS.Test
            {
                TestCode = source,
                ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
                SolutionTransforms =
                {
                    (solution, projectId) => solution.WithProjectCompilationOptions(projectId,
                        ((CSharpCompilationOptions)solution.GetProject(projectId)!.CompilationOptions!).WithAllowUnsafe(true))
                }
            }.RunAsync();
        }
 
        [Fact]
        public async Task SpanParameter_IndexerWithDecrementOperator_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class Test
                {
                    private void DecrementLast(Span<int> buffer)
                    {
                        int length = buffer.Length;
                        buffer[length - 1]--;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_SliceAssignedToLocalAndWritten_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class Test
                {
                    internal static ReadOnlySpan<IntPtr> CopyRuntimeTypeHandles(int[] inHandles, Span<IntPtr> stackScratch)
                    {
                        if (inHandles == null || inHandles.Length == 0)
                        {
                            return default;
                        }
 
                        Span<IntPtr> outHandles = inHandles.Length <= stackScratch.Length ?
                            stackScratch.Slice(0, inHandles.Length) :
                            new IntPtr[inHandles.Length];
                        for (int i = 0; i < inHandles.Length; i++)
                        {
                            outHandles[i] = (IntPtr)inHandles[i];
                        }
                        return outHandles;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ChainedSliceWithIncrementOperator_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class Test
                {
                    private void Method(Span<int> span)
                    {
                        span.Slice(1, 4).Slice(1, 2)[0]++;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInSwitchExpression_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private int M(Span<byte> [|data|], int selector)
                    {
                        return selector switch
                        {
                            0 => data.Length,
                            1 => data[0],
                            _ => data.IsEmpty ? 0 : data[data.Length - 1]
                        };
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private int M(ReadOnlySpan<byte> data, int selector)
                    {
                        return selector switch
                        {
                            0 => data.Length,
                            1 => data[0],
                            _ => data.IsEmpty ? 0 : data[data.Length - 1]
                        };
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInPatternMatching_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private bool M(Span<byte> [|data|])
                    {
                        return data switch
                        {
                            { Length: > 0 } => data[0] == 42,
                            _ => false
                        };
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private bool M(ReadOnlySpan<byte> data)
                    {
                        return data switch
                        {
                            { Length: > 0 } => data[0] == 42,
                            _ => false
                        };
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInNullCoalescingOperator_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private int M(Span<byte> data, bool condition)
                    {
                        Span<byte> local = condition ? data : default;
                        return local.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_MultipleParametersSameType_OnlyReadOnlyOnesMarked()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|source|], Span<byte> destination)
                    {
                        source.CopyTo(destination);
                        destination[0] = 42;
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> source, Span<byte> destination)
                    {
                        source.CopyTo(destination);
                        destination[0] = 42;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInConditionalExpression_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private ReadOnlySpan<byte> M(Span<byte> data1, Span<byte> data2, bool condition)
                    {
                        return condition ? data1 : data2;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInInterpolatedString_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private string M(Span<byte> [|data|])
                    {
                        return $"Length: {data.Length}, First: {(data.IsEmpty ? 0 : data[0])}";
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private string M(ReadOnlySpan<byte> data)
                    {
                        return $"Length: {data.Length}, First: {(data.IsEmpty ? 0 : data[0])}";
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_OnlyAccessedViaLength_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private int M(Memory<byte> [|data|])
                    {
                        return data.Length + data.IsEmpty.GetHashCode();
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private int M(ReadOnlyMemory<byte> data)
                    {
                        return data.Length + data.IsEmpty.GetHashCode();
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ComparedWithOtherSpan_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private bool M(Span<byte> [|data1|], Span<byte> [|data2|])
                    {
                        return data1.SequenceEqual(data2);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private bool M(ReadOnlySpan<byte> data1, ReadOnlySpan<byte> data2)
                    {
                        return data1.SequenceEqual(data2);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInLocalFunction_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        LocalFunc(data);
 
                        static void LocalFunc(Span<byte> d)
                        {
                            Console.WriteLine(d.Length);
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedToStaticMethodWithReadOnlyOverload_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        Helper(data);
                    }
 
                    private static void Helper(ReadOnlySpan<byte> data) => Console.WriteLine(data.Length);
                    private static void Helper(Span<byte> data) => throw new NotImplementedException();
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_WithDefaultParameter_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data, int offset = 0)
                    {
                        var slice = data.Slice(offset);
                        var length = slice.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_NullableReferenceTypeContext_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                #nullable enable
                using System;
 
                class C
                {
                    private void M(Span<string?> [|data|])
                    {
                        foreach (var s in data)
                        {
                            Console.WriteLine(s?.Length ?? 0);
                        }
                    }
                }
                """, """
                #nullable enable
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<string?> data)
                    {
                        foreach (var s in data)
                        {
                            Console.WriteLine(s?.Length ?? 0);
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_NestedGenericType_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
                using System.Collections.Generic;
 
                class C
                {
                    private void M(Span<List<int>> [|data|])
                    {
                        var count = data.Length;
                    }
                }
                """, """
                using System;
                using System.Collections.Generic;
 
                class C
                {
                    private void M(ReadOnlySpan<List<int>> data)
                    {
                        var count = data.Length;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInThrowExpression_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        _ = data.Length > 0 ? data[0] : throw new InvalidOperationException();
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        _ = data.Length > 0 ? data[0] : throw new InvalidOperationException();
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInRecursiveMethod_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private int Sum(Span<int> [|data|])
                    {
                        if (data.IsEmpty) return 0;
                        return data[0] + Sum(data.Slice(1));
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private int Sum(ReadOnlySpan<int> data)
                    {
                        if (data.IsEmpty) return 0;
                        return data[0] + Sum(data.Slice(1));
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedWithMemoryExtensionsIndexOf_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private int M(Span<byte> [|data|])
                    {
                        return data.IndexOf((byte)42);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private int M(ReadOnlySpan<byte> data)
                    {
                        return data.IndexOf((byte)42);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedWithMemoryExtensionsContains_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private bool M(Span<char> [|data|])
                    {
                        return data.Contains('x');
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private bool M(ReadOnlySpan<char> data)
                    {
                        return data.Contains('x');
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedWithStartsWith_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private bool M(Span<byte> [|data|], ReadOnlySpan<byte> prefix)
                    {
                        return data.StartsWith(prefix);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private bool M(ReadOnlySpan<byte> data, ReadOnlySpan<byte> prefix)
                    {
                        return data.StartsWith(prefix);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_AccessedViaExplicitInterfaceCast_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
                using System.Collections.Generic;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        IEnumerable<byte> enumerable = data.ToArray();
                        foreach (var b in enumerable) { }
                    }
                }
                """, """
                using System;
                using System.Collections.Generic;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        IEnumerable<byte> enumerable = data.ToArray();
                        foreach (var b in enumerable) { }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_PassedToGenericMethodConstrainedToSpan_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        Helper(data);
                    }
 
                    private void Helper<T>(Span<T> data)
                    {
                        data[0] = default;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInUsingStatement_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        var copy = data; // Local copy prevents analysis
                        Console.WriteLine(copy.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_WrittenViaCompoundAssignment_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<int> data)
                    {
                        if (data.Length > 0)
                        {
                            data[0] += 5;
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_WrittenViaPrefixIncrement_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<int> data)
                    {
                        if (data.Length > 0)
                        {
                            ++data[0];
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_WrittenViaPostfixIncrement_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<int> data)
                    {
                        if (data.Length > 0)
                        {
                            data[0]++;
                        }
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_Clear_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        data.Clear();
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_Fill_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> data)
                    {
                        data.Fill(0);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_Reverse_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<int> data)
                    {
                        data.Reverse();
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_Sort_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<int> data)
                    {
                        data.Sort();
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_AccessSpanAndWrite_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private void M(Memory<byte> data)
                    {
                        var span = data.Span;
                        span[0] = 42;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_ImplicitOperatorToReadOnlySpan_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        ReadOnlySpan<byte> ros = data;
                        Console.WriteLine(ros.Length);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        ReadOnlySpan<byte> ros = data;
                        Console.WriteLine(ros.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_UsedInConstructor_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                ref struct SpanWrapper
                {
                    public ReadOnlySpan<byte> Data;
                    
                    public SpanWrapper(ReadOnlySpan<byte> data)
                    {
                        Data = data;
                    }
                }
 
                class C
                {
                    private SpanWrapper M(Span<byte> [|data|])
                    {
                        return new SpanWrapper(data);
                    }
                }
                """, """
                using System;
 
                ref struct SpanWrapper
                {
                    public ReadOnlySpan<byte> Data;
                    
                    public SpanWrapper(ReadOnlySpan<byte> data)
                    {
                        Data = data;
                    }
                }
 
                class C
                {
                    private SpanWrapper M(ReadOnlySpan<byte> data)
                    {
                        return new SpanWrapper(data);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_TryCopyToWithoutWrite_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private bool M(Span<byte> [|data|])
                    {
                        Span<byte> destination = stackalloc byte[10];
                        return data.TryCopyTo(destination);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private bool M(ReadOnlySpan<byte> data)
                    {
                        Span<byte> destination = stackalloc byte[10];
                        return data.TryCopyTo(destination);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_GetPinnableReference_NoDiagnostic()
        {
            await VerifyAnalyzerAsync("""
                using System;
 
                class C
                {
                    private unsafe void M(Span<byte> data)
                    {
                        ref byte r = ref data.GetPinnableReference();
                        byte b = r;
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_AssignedBackToItself_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private void M(Span<byte> [|data|])
                    {
                        data = data.Slice(1);
                        Console.WriteLine(data.Length);
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private void M(ReadOnlySpan<byte> data)
                    {
                        data = data.Slice(1);
                        Console.WriteLine(data.Length);
                    }
                }
                """);
        }
 
        [Fact]
        public async Task SpanParameter_OverloadedOperatorEquals_ProducesDiagnostic()
        {
            await VerifyFixerAsync("""
                using System;
 
                class C
                {
                    private bool M(Span<byte> [|data1|], Span<byte> [|data2|])
                    {
                        return data1 == data2; // Uses overloaded == operator
                    }
                }
                """, """
                using System;
 
                class C
                {
                    private bool M(ReadOnlySpan<byte> data1, ReadOnlySpan<byte> data2)
                    {
                        return data1 == data2; // Uses overloaded == operator
                    }
                }
                """);
        }
 
        [Fact]
        public async Task MemoryParameter_PinMethod_ProducesDiagnostic()
        {
            await new VerifyCS.Test
            {
                TestCode = """
                    using System;
                    using System.Buffers;
 
                    class C
                    {
                        private unsafe void M(Memory<byte> [|data|])
                        {
                            using (MemoryHandle handle = data.Pin())
                            {
                                byte* ptr = (byte*)handle.Pointer;
                                if (ptr != null)
                                {
                                    *ptr = 42;
                                }
                            }
                        }
                    }
                    """,
                FixedCode = """
                    using System;
                    using System.Buffers;
 
                    class C
                    {
                        private unsafe void M(ReadOnlyMemory<byte> data)
                        {
                            using (MemoryHandle handle = data.Pin())
                            {
                                byte* ptr = (byte*)handle.Pointer;
                                if (ptr != null)
                                {
                                    *ptr = 42;
                                }
                            }
                        }
                    }
                    """,
                ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
                SolutionTransforms =
                {
                    (solution, projectId) => solution.WithProjectCompilationOptions(projectId,
                        ((CSharpCompilationOptions)solution.GetProject(projectId)!.CompilationOptions!).WithAllowUnsafe(true))
                }
            }.RunAsync();
        }
 
        private static async Task VerifyAnalyzerAsync(
            [StringSyntax("C#-test")] string source,
            LanguageVersion languageVersion = LanguageVersion.Default)
        {
            await new VerifyCS.Test
            {
                TestCode = source,
                ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
                LanguageVersion = languageVersion,
            }.RunAsync();
        }
 
        private static async Task VerifyFixerAsync(
            [StringSyntax("C#-test")] string source,
            [StringSyntax("C#-test")] string fixedSource,
            LanguageVersion languageVersion = LanguageVersion.Default)
        {
            await new VerifyCS.Test
            {
                TestCode = source,
                FixedCode = fixedSource,
                ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
                LanguageVersion = languageVersion,
            }.RunAsync();
        }
    }
}