diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md index facf822421c6..91936a60379b 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1908,6 +1908,18 @@ In many situations, logging is disabled or set to a log level that results in an |CodeFix|True| --- +## [CA1876](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876): Use 'ReadOnlySpan\' or 'ReadOnlyMemory\' instead of 'Span\' or 'Memory\' + +Using 'ReadOnlySpan\' or 'ReadOnlyMemory\' instead of 'Span\' or 'Memory\' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif index a3e0dd7859aa..65fe41e4c9f4 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3507,6 +3507,26 @@ ] } }, + "CA1876": { + "id": "CA1876", + "shortDescription": "Use 'ReadOnlySpan' or 'ReadOnlyMemory' instead of 'Span' or 'Memory'", + "fullDescription": "Using 'ReadOnlySpan' or 'ReadOnlyMemory' instead of 'Span' or 'Memory' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "PreferReadOnlySpanOverSpanAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md index 0ecf6597797e..4dcac0548c09 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md @@ -7,6 +7,7 @@ Rule ID | Category | Severity | Notes CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873) CA1874 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874) CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875) +CA1876 | Performance | Info | PreferReadOnlySpanOverSpanAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876) CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023) CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024) CA2025 | Reliability | Disabled | DoNotPassDisposablesIntoUnawaitedTasksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2025) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 55948fcbc88d..7eda0ea3a659 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2216,4 +2216,16 @@ Widening and user defined conversions are not supported with generic types. Replace obsolete call + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + Change to '{0}' + diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs new file mode 100644 index 000000000000..eec3fca9b678 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + /// + /// CA1876: Use ReadOnlySpan<T> or ReadOnlyMemory<T> instead of Span<T> or Memory<T> + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferReadOnlySpanOverSpanFixer))] + [Shared] + public sealed class PreferReadOnlySpanOverSpanFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(PreferReadOnlySpanOverSpanAnalyzer.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var node = root.FindNode(context.Span, getInnermostNodeForTie: true); + var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + + var diagnostic = context.Diagnostics[0]; + + if (semanticModel.GetDeclaredSymbol(node, context.CancellationToken) is not IParameterSymbol parameterSymbol || + GetReadOnlyTypeName(parameterSymbol.Type) is not { } targetTypeName) + { + return; + } + + var title = string.Format(MicrosoftNetCoreAnalyzersResources.PreferReadOnlySpanOverSpanCodeFixTitle, targetTypeName); + + context.RegisterCodeFix( + CodeAction.Create( + title: title, + createChangedDocument: c => ChangeParameterTypeAsync(context.Document, node, c), + equivalenceKey: title), + diagnostic); + } + + private static string? GetReadOnlyTypeName(ITypeSymbol typeSymbol) + { + return typeSymbol is INamedTypeSymbol namedType && namedType.OriginalDefinition.Name is "Span" or "Memory" ? + $"ReadOnly{typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}" : + null; + } + + private static async Task ChangeParameterTypeAsync( + Document document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // Get the parameter symbol to construct the correct type + var parameterSymbol = semanticModel.GetDeclaredSymbol(node, cancellationToken) as IParameterSymbol; + if (parameterSymbol?.Type is not INamedTypeSymbol namedType || namedType.TypeArguments.Length != 1) + { + return document; + } + + // Get the compilation to find the readonly types + var compilation = semanticModel.Compilation; + var typeName = namedType.OriginalDefinition.Name; + INamedTypeSymbol? readOnlyType = null; + + if (typeName == "Span") + { + readOnlyType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); + } + else if (typeName == "Memory") + { + readOnlyType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1); + } + + if (readOnlyType == null) + { + return document; + } + + // Construct the generic type with the same type argument + var newType = readOnlyType.Construct(namedType.TypeArguments[0]); + var newTypeNode = generator.TypeExpression(newType); + + // Replace the parameter's type + editor.ReplaceNode(node, (currentNode, gen) => + { + return gen.WithType(currentNode, newTypeNode); + }); + + return editor.GetChangedDocument(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.cs new file mode 100644 index 000000000000..c2c55c5f1421 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.cs @@ -0,0 +1,481 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.Lightup; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1876: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferReadOnlySpanOverSpanAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1876"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferReadOnlySpanOverSpanTitle)), + CreateLocalizableResourceString(nameof(PreferReadOnlySpanOverSpanMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + description: CreateLocalizableResourceString(nameof(PreferReadOnlySpanOverSpanDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + var compilation = context.Compilation; + var span = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemSpan1); + var readOnlySpan = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); + var memory = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemory1); + var readOnlyMemory = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1); + + if (span == null || readOnlySpan == null || memory == null || readOnlyMemory == null) + { + return; + } + + context.RegisterOperationBlockStartAction(blockStartContext => + { + // Skip if not a method, is override, is interface implementation, or doesn't match visibility + if (blockStartContext.OwningSymbol is not IMethodSymbol methodSymbol || + methodSymbol.IsOverride || + methodSymbol.IsImplementationOfAnyInterfaceMember() || + !blockStartContext.Options.MatchesConfiguredVisibility(Rule, methodSymbol, compilation, + defaultRequiredVisibility: SymbolVisibilityGroup.Internal | SymbolVisibilityGroup.Private)) + { + return; + } + + // Track which parameters are writable Span/Memory types and whether they're written to + ConcurrentDictionary? candidateParameters = null; + + foreach (var parameter in methodSymbol.Parameters) + { + if (IsConvertibleSpanOrMemoryParameter(parameter, span, memory, readOnlySpan, readOnlyMemory, out var readOnlyType) && readOnlyType != null) + { + candidateParameters ??= new ConcurrentDictionary(SymbolEqualityComparer.Default); + candidateParameters.TryAdd(parameter, readOnlyType); + } + } + + if (candidateParameters == null) + { + return; + } + + // Check parameter references for writes + blockStartContext.RegisterOperationAction(operationContext => + { + if (operationContext.Operation is IParameterReferenceOperation parameterReference && + candidateParameters.ContainsKey(parameterReference.Parameter)) + { + var valueUsage = operationContext.Operation.GetValueUsageInfo(methodSymbol); + + // If the parameter is written to or has a writable reference, remove it from candidates + if ((valueUsage & (ValueUsageInfo.Write | ValueUsageInfo.WritableReference)) != 0) + { + candidateParameters.TryRemove(parameterReference.Parameter, out _); + } + } + }, OperationKind.ParameterReference); + + // Check for increment/decrement operations that may modify indexer values + // This handles: span[0]++, span.Slice()[0]--, etc. + blockStartContext.RegisterOperationAction(operationContext => + { + var incDecOp = (IIncrementOrDecrementOperation)operationContext.Operation; + // Check if any candidate parameter is used in the target + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(incDecOp.Target, kvp.Key)) + { + candidateParameters.TryRemove(kvp.Key, out _); + break; + } + } + }, OperationKind.Increment, OperationKind.Decrement); + + // Check for writes through property-based indexers (Span uses property indexers) + blockStartContext.RegisterOperationAction(operationContext => + { + var propertyRef = (IPropertyReferenceOperation)operationContext.Operation; + + // Find which candidate parameter (if any) is used in this property reference instance + // This handles both direct references (span[0]) and chained calls (span.Slice()[0]) + IParameterSymbol? affectedParameter = null; + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(propertyRef.Instance, kvp.Key)) + { + affectedParameter = kvp.Key; + break; + } + } + + if (affectedParameter == null) + { + return; + } + + // Check if this property reference is on the left side of an assignment + // This handles: span[0] = value, span[0] += value, etc. + if (propertyRef.Parent is IAssignmentOperation assignment && assignment.Target == propertyRef) + { + candidateParameters.TryRemove(affectedParameter, out _); + return; + } + + // Check if this property reference is being passed as ref/out argument + // This handles cases like: SwapIfGreater(ref span[0], ref span[1]) + if (propertyRef.Parent is IArgumentOperation argument && + argument.Parameter?.RefKind is RefKind.Ref or RefKind.Out) + { + candidateParameters.TryRemove(affectedParameter, out _); + return; + } + + // Check if this property reference is used in a ref variable declaration + // This handles cases like: ref int i = ref span[0]; + // Walk up the parent chain to find a VariableDeclaratorOperation with RefKind + var parent = propertyRef.Parent; + while (parent != null) + { + if (parent is IVariableDeclaratorOperation variableDeclarator && + variableDeclarator.Symbol.RefKind != RefKind.None) + { + candidateParameters.TryRemove(affectedParameter, out _); + return; + } + if (parent is IBlockOperation or IMethodBodyOperation) + { + // Stop at block/method boundaries + break; + } + parent = parent.Parent; + } + + // Check if this property reference is being returned by ref + // This handles cases where span[0] is being returned by ref + var containingMethod = operationContext.ContainingSymbol as IMethodSymbol; + if (containingMethod?.ReturnsByRef == true && + propertyRef.Parent is IReturnOperation) + { + candidateParameters.TryRemove(affectedParameter, out _); + } + }, OperationKind.PropertyReference); + + // Check for writes through implicit indexers (Index/Range operators like span[^1] or span[1..5]) + blockStartContext.RegisterOperationAction(operationContext => + { + // Get the instance from the first child of the implicit indexer reference + var instance = operationContext.Operation.Children.FirstOrDefault(); + + // Check if this element reference is on the left side of an assignment + if (instance is IParameterReferenceOperation paramRef && + candidateParameters.ContainsKey(paramRef.Parameter) && + operationContext.Operation.Parent is IAssignmentOperation assignment && + assignment.Target == operationContext.Operation) + { + candidateParameters.TryRemove(paramRef.Parameter, out _); + } + }, OperationKindEx.ImplicitIndexerReference); + + // Check for parameters passed to methods that might write to them + blockStartContext.RegisterOperationAction(operationContext => + { + var argument = (IArgumentOperation)operationContext.Operation; + + // Check if any of our candidate parameters are used in this argument + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(argument.Value, kvp.Key) && + argument.Parameter?.Type is INamedTypeSymbol argType && + (SymbolEqualityComparer.Default.Equals(argType.OriginalDefinition, span) || + SymbolEqualityComparer.Default.Equals(argType.OriginalDefinition, memory))) + { + // Target expects writable Span/Memory, so this parameter must remain writable + candidateParameters.TryRemove(kvp.Key, out _); + } + } + }, OperationKind.Argument); + + // Check for invocations where the parameter is used + blockStartContext.RegisterOperationAction(operationContext => + { + var invocation = (IInvocationOperation)operationContext.Operation; + + // Check if this extension method's first parameter (the 'this' parameter) is a Span/Memory + // If the extension method takes Span or Memory (not readonly versions), + // then the parameter must remain writable + if (invocation.TargetMethod.IsExtensionMethod && + invocation.Arguments.Length > 0) + { + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(invocation.Arguments[0].Value, kvp.Key) && + invocation.TargetMethod.Parameters[0].Type is INamedTypeSymbol firstParamNamedType && + (SymbolEqualityComparer.Default.Equals(firstParamNamedType.OriginalDefinition, span) || + SymbolEqualityComparer.Default.Equals(firstParamNamedType.OriginalDefinition, memory))) + { + candidateParameters.TryRemove(kvp.Key, out _); + } + } + } + + // Check if method returns non-readonly Span/Memory and that result is used incompatibly + // E.g., Slice() returns Span, and that Span is assigned to a local that's written to + if (invocation.TargetMethod.ReturnType is INamedTypeSymbol returnType && + (SymbolEqualityComparer.Default.Equals(returnType.OriginalDefinition, span) || + SymbolEqualityComparer.Default.Equals(returnType.OriginalDefinition, memory))) + { + // If a method returns non-readonly Span/Memory, check how it's used + // If assigned to a variable, that variable may be written to + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(invocation, kvp.Key)) + { + // Walk up the parent chain to find if this is eventually assigned to a local + IOperation? current = invocation; + while (current != null) + { + // Check if parent is an assignment to a local variable + if (current.Parent is ISimpleAssignmentOperation assignment) + { + // Check if assigned to a local Span/Memory variable + if (assignment.Target is ILocalReferenceOperation localRef && + localRef.Local.Type is INamedTypeSymbol localType && + (SymbolEqualityComparer.Default.Equals(localType.OriginalDefinition, span) || + SymbolEqualityComparer.Default.Equals(localType.OriginalDefinition, memory))) + { + // Conservatively assume local may be written to + candidateParameters.TryRemove(kvp.Key, out _); + break; + } + } + + // Move up to parent, but stop at certain boundaries + if (current.Parent is IBlockOperation || + current.Parent is IMethodBodyOperation || + current.Parent == null) + { + break; + } + + current = current.Parent; + } + } + } + } + }, OperationKind.Invocation); + + // Check for parameters returned from the method + blockStartContext.RegisterOperationAction(operationContext => + { + var returnOp = (IReturnOperation)operationContext.Operation; + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(returnOp.ReturnedValue, kvp.Key)) + { + // If method returns by ref, the parameter must remain writable + if (methodSymbol.ReturnsByRef) + { + candidateParameters.TryRemove(kvp.Key, out _); + continue; + } + + // Returning the parameter means it escapes, so we need to check if return type is compatible + if (methodSymbol.ReturnType is INamedTypeSymbol returnNamedType && + !SymbolEqualityComparer.Default.Equals(returnNamedType.OriginalDefinition, readOnlySpan) && + !SymbolEqualityComparer.Default.Equals(returnNamedType.OriginalDefinition, readOnlyMemory)) + { + // Return type requires writable Span/Memory + candidateParameters.TryRemove(kvp.Key, out _); + } + } + } + }, OperationKind.Return); + + // Check for parameters stored in fields or properties + blockStartContext.RegisterOperationAction(operationContext => + { + var fieldRef = (IFieldReferenceOperation)operationContext.Operation; + if (fieldRef.Parent is IAssignmentOperation assignment && + assignment.Target == fieldRef && + assignment.Value is IParameterReferenceOperation paramRef && + candidateParameters.ContainsKey(paramRef.Parameter)) + { + // Parameter is being stored to a field - must remain writable if field type is not readonly + if (fieldRef.Field.Type is INamedTypeSymbol fieldNamedType && + !SymbolEqualityComparer.Default.Equals(fieldNamedType.OriginalDefinition, readOnlySpan) && + !SymbolEqualityComparer.Default.Equals(fieldNamedType.OriginalDefinition, readOnlyMemory)) + { + candidateParameters.TryRemove(paramRef.Parameter, out _); + } + } + }, OperationKind.FieldReference); + + // Check for assignments where parameter is the value being assigned + blockStartContext.RegisterOperationAction(operationContext => + { + var assignment = (IAssignmentOperation)operationContext.Operation; + // Check if the target type is compatible with readonly version + if (assignment.Target.Type is INamedTypeSymbol targetNamedType && + !SymbolEqualityComparer.Default.Equals(targetNamedType.OriginalDefinition, readOnlySpan) && + !SymbolEqualityComparer.Default.Equals(targetNamedType.OriginalDefinition, readOnlyMemory)) + { + // Target expects writable Span/Memory - check if any parameter is used in the value + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(assignment.Value, kvp.Key)) + { + candidateParameters.TryRemove(kvp.Key, out _); + } + } + } + }, OperationKind.SimpleAssignment); + + // Check for array creation where parameter is an element + blockStartContext.RegisterOperationAction(operationContext => + { + var arrayCreation = (IArrayCreationOperation)operationContext.Operation; + if (arrayCreation.Initializer != null && arrayCreation.Type is IArrayTypeSymbol arrayType) + { + foreach (var element in arrayCreation.Initializer.ElementValues) + { + // Check if array element type is compatible with readonly version + if (element is IParameterReferenceOperation paramRef && + candidateParameters.ContainsKey(paramRef.Parameter) && + arrayType.ElementType is INamedTypeSymbol elementNamedType && + !SymbolEqualityComparer.Default.Equals(elementNamedType.OriginalDefinition, readOnlySpan) && + !SymbolEqualityComparer.Default.Equals(elementNamedType.OriginalDefinition, readOnlyMemory)) + { + // Array expects writable Span/Memory elements + candidateParameters.TryRemove(paramRef.Parameter, out _); + } + } + } + }, OperationKind.ArrayCreation); + + // Check for AddressOf operations (used in fixed statements) - parameters used should not be flagged + blockStartContext.RegisterOperationAction(operationContext => + { + if (operationContext.Operation is IAddressOfOperation addressOf) + { + // Check if any of our candidate parameters are used in this address-of operation + foreach (var kvp in candidateParameters) + { + if (ContainsParameterReference(addressOf, kvp.Key)) + { + // Parameter address is taken, must remain writable + candidateParameters.TryRemove(kvp.Key, out _); + } + } + } + }, OperationKind.AddressOf); + + blockStartContext.RegisterOperationBlockEndAction(blockEndContext => + { + // Report diagnostics for parameters that were never written to + foreach (var kvp in candidateParameters) + { + var parameter = kvp.Key; + var readOnlyType = kvp.Value; + + var diagnostic = parameter.CreateDiagnostic( + Rule, + parameter.Name, + readOnlyType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + parameter.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); + blockEndContext.ReportDiagnostic(diagnostic); + } + }); + }); + } + + private static bool ContainsParameterReference(IOperation? operation, IParameterSymbol parameter) + { + if (operation == null) + { + return false; + } + + if (operation is IParameterReferenceOperation paramRef && + SymbolEqualityComparer.Default.Equals(paramRef.Parameter, parameter)) + { + return true; + } + + foreach (var child in operation.Children) + { + if (ContainsParameterReference(child, parameter)) + { + return true; + } + } + + return false; + } + + private static bool IsConvertibleSpanOrMemoryParameter( + IParameterSymbol parameter, + INamedTypeSymbol span, + INamedTypeSymbol memory, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory, + out INamedTypeSymbol? readOnlyType) + { + readOnlyType = null; + + if (parameter.Type is not INamedTypeSymbol namedType) + { + return false; + } + + var originalDefinition = namedType.OriginalDefinition; + + if (SymbolEqualityComparer.Default.Equals(originalDefinition, span)) + { + // Span -> ReadOnlySpan + if (namedType.TypeArguments.Length == 1) + { + readOnlyType = readOnlySpan.Construct(namedType.TypeArguments[0]); + return true; + } + } + else if (SymbolEqualityComparer.Default.Equals(originalDefinition, memory)) + { + // Memory -> ReadOnlyMemory + if (namedType.TypeArguments.Length == 1) + { + readOnlyType = readOnlyMemory.Construct(namedType.TypeArguments[0]); + return true; + } + } + + return false; + } + + + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index d5f92baf02e5..e8ed6ae5393b 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2258,6 +2258,26 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj Upřednostňujte porovnání vlastnosti Length s 0 místo použití metody Any(), a to jak pro přehlednost, tak pro výkon. + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream má přetížení ReadAsync, které jako první argument přijímá Memory<Byte>, a přetížení WriteAsync, které jako první argument přijímá ReadOnlyMemory<Byte>. Upřednostňujte volání přetížení založených na paměti, která jsou efektivnější. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index f63f006d064d..b8be313da464 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2258,6 +2258,26 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Sowohl aus Gründen der Klarheit als auch der Leistung ist der Vergleich von „Length“ mit 0 der Verwendung von „Any()“ vorzuziehen + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream verfügt über eine Überladung "ReadAsync", die "Memory<Byte>" als erstes Argument akzeptiert, sowie über eine Überladung "WriteAsync", die "ReadOnlyMemory<Byte>" als erstes Argument akzeptiert. Rufen Sie möglichst arbeitsspeicherbasierte Überladungen auf, da diese effizienter sind. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index a03a7a0fa4d0..ee23d8007364 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2258,6 +2258,26 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Es preferible comparar "Length" con 0 en lugar de usar "Any()", tanto por claridad como por rendimiento. + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" tiene una sobrecarga "ReadAsync" que toma "Memory<Byte>" como primer argumento y una sobrecarga "WriteAsync" que toma "ReadOnlyMemory<Byte>" como primer argumento. Es preferible llamar a las sobrecargas basadas en memory, que son más eficaces. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 40c160bb5b5f..ecdfe66b7f86 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2258,6 +2258,26 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Préférez comparer 'Length' à 0 au lieu d’utiliser 'Any()', à la fois pour plus de clarté et pour des performances + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream’ a une surcharge ’ReadAsync’ qui prend un ’Memory<Byte>' comme premier argument et une surcharge ’WriteAsync’ qui prend un ’ReadOnlyMemory<Byte>' comme premier argument. Préférez l'appel des surcharges basées sur la mémoire, car elles sont plus efficaces. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 924a482d9f5d..8a53726743b9 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2258,6 +2258,26 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Preferire il confronto 'Length' con 0 anziché usare 'Any()', sia per chiarezza che per prestazioni + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' contiene un overload 'ReadAsync' che accetta 'Memory<Byte>' come primo argomento e un overload 'WriteAsync' che accetta 'ReadOnlyMemory<Byte>' come primo argomento. Per la chiamata preferire gli overload basati su Memory, che sono più efficaci. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6c3fbfeccd9a..63a8f310c955 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2258,6 +2258,26 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 明確性とパフォーマンスの両方のために、'Any()' を使用するのではなく、'Length' を 0 と比較することを優先してください + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' には、最初の引数として 'Memory<Byte>' を取る 'ReadAsync' オーバーロードと、最初の引数として 'ReadOnlyMemory<Byte>' を取る 'WriteAsync' オーバーロードがあります。より効率的なメモリ ベースのオーバーロードを呼び出すことをお勧めします。 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index f0706403c0d4..21a0f30abc1a 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2258,6 +2258,26 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 명확성과 성능을 위해 'Any()'를 사용하는 것보다 'Length'를 0과 비교하는 것이 좋습니다. + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream'에 첫 번째 인수로 'Memory<Byte>'를 사용하는 'ReadAsync' 오버로드와 첫 번째 인수로 'ReadOnlyMemory<Byte>'를 사용하는 'WriteAsync' 오버로드가 있습니다. 더 효율적인 메모리 기반 오버로드를 호출하는 것이 좋습니다. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 51f4dee3e919..daf99c9dc487 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2258,6 +2258,26 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Preferuj porównywanie wartości „Length” z wartością 0 zamiast używania elementu „Any()”, zarówno w celu zapewnienia przejrzystości, jak i wydajności + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Element „Stream” ma przeciążenie „ReadAsync”, które przyjmuje „Memory<Byte>” jako pierwszy argument i przeciążenie „WriteAsync”, które przyjmuje „ReadOnlyMemory<Byte>” jako pierwszy argument. Preferuj wywoływanie przeciążeń opartych na pamięci, co jest bardziej wydajne. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index cb9dea8edaf1..9445a3340e48 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2258,6 +2258,26 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com Prefira comparar 'Length' com 0 em vez de usar 'Any()', tanto para clareza quanto para desempenho + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' tem uma sobrecarga 'ReadAsync' que recebe um 'Memory<Byte>' como o primeiro argumento e uma sobrecarga 'WriteAsync' que recebe um 'ReadOnlyMemory<Byte>' como o primeiro argumento. Prefira chamar as sobrecargas com base na memória, que são mais eficientes. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 57538a5cb231..ed272bf3cf25 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2258,6 +2258,26 @@ Widening and user defined conversions are not supported with generic types.Для ясности и для обеспечения производительности старайтесь сравнивать 'Length' с 0 вместо того, чтобы использовать 'Any()' + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" содержит перегрузку "ReadAsync", которая принимает в качестве первого аргумента "Memory<Byte>", и перегрузку "WriteAsync", которая принимает в качестве первого аргумента "ReadOnlyMemory<Byte>". Старайтесь использовать перегрузки на основе памяти, которые являются более эффективными. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index de2ea9f502e1..ae8dabdc2c40 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2258,6 +2258,26 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Hem kolay anlaşılırlık hem de performans için 'Length' değerini 'Any()' yerine 0 ile karşılaştırmayı tercih edin + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream', ilk bağımsız değişken olarak 'Memory<Byte>' alan bir 'ReadAsync' aşırı yüklemesine ve ilk bağımsız değişken olarak 'ReadOnlyMemory<Byte>' alan bir 'WriteAsync' aşırı yüklemesine sahip. Daha verimli olan bellek tabanlı aşırı yüklemeleri çağırmayı tercih edin. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 68f722d64e4f..df96d7b73479 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2258,6 +2258,26 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin 为了清楚起见和提高性能,首选将 'Length'与 0 进行比较,而不是使用 'Any()'。 + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" 有一个将 "Memory<Byte>" 作为第一个参数的 "ReadAsync" 重载和一个将 "Memory<Byte>" 作为第一个参数的 "WriteAsync" 重载。首选调用基于内存的重载,它们的效率更高。 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 43db515c0d4b..f9455147a2d5 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2258,6 +2258,26 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 為了清楚明瞭和為了提升效能,偏好比較 'Length' 與 0,而不是使用 'Any()' + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' 具有採用 'Memory<Byte>' 作為第一個引數的 'ReadAsync' 多載,以及採用 'ReadOnlyMemory<Byte>' 作為第一個引數的 'WriteAsync' 多載。建議呼叫採用記憶體的多載,較有效率。 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 84d171e834bd..f6ef93d0b2f9 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1875 +Performance: HA, CA1800-CA1876 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265 Naming: CA1700-CA1727 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpanTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpanTests.cs new file mode 100644 index 000000000000..8b5721a967e4 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpanTests.cs @@ -0,0 +1,1429 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +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() + { + string source = """ + using System; + + class C + { + private void M(Span [|data|]) + { + var length = data.Length; + } + } + """; + string expected = """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + var length = data.Length; + } + } + """; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task MemoryParameter_NotWritten_ProducesDiagnostic() + { + string source = """ + using System; + + class C + { + private void M(Memory [|data|]) + { + var span = data.Span; + var length = span.Length; + } + } + """; + string expected = """ + using System; + + class C + { + private void M(ReadOnlyMemory data) + { + var span = data.Span; + var length = span.Length; + } + } + """; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_Written_NoDiagnostic() + { + string source = """ + using System; + + class C + { + private void M(Span data) + { + data[0] = 1; + } + } + """; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_WrittenViaIndexer_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data) + { + for (int i = 0; i < data.Length; i++) + { + data[i] = i; + } + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_PassedAsRefParameter_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data) + { + Helper(ref data); + } + + private void Helper(ref Span data) + { + data[0] = 1; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task PublicMethod_DefaultConfig_NoDiagnostic() + { + string source = @" +using System; + +public class C +{ + public void M(Span data) + { + var length = data.Length; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task OverrideMethod_NoDiagnostic() + { + string source = @" +using System; + +public class Base +{ + public virtual void M(Span data) { } +} + +public class Derived : Base +{ + public override void M(Span data) + { + var length = data.Length; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task InterfaceImplementation_NoDiagnostic() + { + string source = @" +using System; + +public interface I +{ + void M(Span data); +} + +public class C : I +{ + public void M(Span data) + { + var length = data.Length; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task ReadOnlySpanParameter_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var length = data.Length; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_PassedToMethodReadOnly_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + var length = data.Length; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + var length = data.Length; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_SlicedButNotWritten_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var slice = data.Slice(0, 10); + var length = slice.Length; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var slice = data.Slice(0, 10); + var length = slice.Length; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task MultipleParameters_MixedUsage() + { + string source = @" +using System; + +class C +{ + private void M(Span [|readOnlyData|], Span writableData) + { + var length = readOnlyData.Length; + writableData[0] = 1; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan readOnlyData, Span writableData) + { + var length = readOnlyData.Length; + writableData[0] = 1; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_PassedToWritableSpanMethod_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data) + { + Helper(data); + } + + private void Helper(Span data) + { + data[0] = 1; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_CopiedToLocal_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var copy = data; + Console.WriteLine(copy.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var copy = data; + Console.WriteLine(copy.Length); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_UsedInForEachLoop_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + foreach (var b in data) + { + Console.WriteLine(b); + } + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + foreach (var b in data) + { + Console.WriteLine(b); + } + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_PassedAsOutArgument_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data) + { + Helper(out data); + } + + private void Helper(out Span data) + { + data = default; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task MemoryParameter_AccessSpanProperty_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Memory [|data|]) + { + var s = data.Span; + Console.WriteLine(s[0]); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlyMemory data) + { + var s = data.Span; + Console.WriteLine(s[0]); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_UsedInLinqQuery_ProducesDiagnostic() + { + string source = @" +using System; +using System.Linq; + +class C +{ + private void M(Span [|data|]) + { + var sum = data.ToArray().Sum(); + } +} +"; + string expected = @" +using System; +using System.Linq; + +class C +{ + private void M(ReadOnlySpan data) + { + var sum = data.ToArray().Sum(); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_ReadThroughIndexer_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var first = data[0]; + var last = data[data.Length - 1]; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var first = data[0]; + var last = data[data.Length - 1]; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_InTernaryExpression_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|], bool condition) + { + var length = condition ? data.Length : 0; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data, bool condition) + { + var length = condition ? data.Length : 0; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_PassedToGenericMethod_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + Console.WriteLine(data.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + Console.WriteLine(data.Length); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_UsedInReturnStatement_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private ReadOnlySpan M(Span [|data|]) + { + return data; + } +} +"; + string expected = @" +using System; + +class C +{ + private ReadOnlySpan M(ReadOnlySpan data) + { + return data; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task MemoryParameter_SliceAndRead_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Memory [|data|]) + { + var slice = data.Slice(1, 5); + Console.WriteLine(slice.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlyMemory data) + { + var slice = data.Slice(1, 5); + Console.WriteLine(slice.Length); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_ConditionalAccess_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var result = data.IsEmpty ? 0 : data[0]; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var result = data.IsEmpty ? 0 : data[0]; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_MultipleReferences_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + Console.WriteLine(data.Length); + Console.WriteLine(data[0]); + var slice = data.Slice(1); + Console.WriteLine(slice.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + Console.WriteLine(data.Length); + Console.WriteLine(data[0]); + var slice = data.Slice(1); + Console.WriteLine(slice.Length); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_CopyTo_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + Span destination = stackalloc byte[10]; + data.CopyTo(destination); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + Span destination = stackalloc byte[10]; + data.CopyTo(destination); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_TryCopyTo_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + Span destination = stackalloc byte[10]; + data.TryCopyTo(destination); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + Span destination = stackalloc byte[10]; + data.TryCopyTo(destination); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_IndexOperator_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data) + { + data[^1] = 5; // Write using Index operator + } +} +"; + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_IndexOperatorRead_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var last = data[^1]; // Read using Index operator + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var last = data[^1]; // Read using Index operator + } +} +"; + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = expected, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_RangeOperator_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var slice = data[1..5]; // Read using Range operator + Console.WriteLine(slice.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var slice = data[1..5]; // Read using Range operator + Console.WriteLine(slice.Length); + } +} +"; + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = expected, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_RangeFromEndOperator_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span [|data|]) + { + var slice = data[^3..^1]; // Read using Range with Index + Console.WriteLine(slice.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlySpan data) + { + var slice = data[^3..^1]; // Read using Range with Index + Console.WriteLine(slice.Length); + } +} +"; + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = expected, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_ReturnedFromMethod_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private Span M(Span data) + { + return data; // Returning non-readonly type + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_ReturnedAsReadOnlySpan_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private ReadOnlySpan M(Span [|data|]) + { + return data; // Returning as readonly + } +} +"; + string expected = @" +using System; + +class C +{ + private ReadOnlySpan M(ReadOnlySpan data) + { + return data; // Returning as readonly + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_StoredInRefParameter_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data, ref Span output) + { + output = data; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_StoredInOutParameter_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data, out Span output) + { + output = data; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task MemoryParameter_StoredInField_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private Memory _field; + + private void M(Memory data) + { + _field = data; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task MemoryParameter_StoredInReadOnlyMemoryField_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private ReadOnlyMemory _field; + + private void M(Memory [|data|]) + { + _field = data; + } +} +"; + string expected = @" +using System; + +class C +{ + private ReadOnlyMemory _field; + + private void M(ReadOnlyMemory data) + { + _field = data; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task MemoryParameter_StoredInArray_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Memory data) + { + var array = new Memory[] { data }; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task MemoryParameter_StoredInReadOnlyMemoryArray_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Memory [|data|]) + { + var array = new ReadOnlyMemory[] { data }; + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlyMemory data) + { + var array = new ReadOnlyMemory[] { data }; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_StoredInProperty_NoDiagnostic() + { + string source = @" +using System; + +ref struct Container +{ + public Span Data { get; set; } +} + +class C +{ + private void M(Span data) + { + var container = new Container { Data = data }; + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task SpanParameter_StoredInReadOnlySpanProperty_ProducesDiagnostic() + { + string source = @" +using System; + +ref struct Container +{ + public ReadOnlySpan Data { get; set; } +} + +class C +{ + private void M(Span [|data|]) + { + var container = new Container { Data = data }; + } +} +"; + string expected = @" +using System; + +ref struct Container +{ + public ReadOnlySpan Data { get; set; } +} + +class C +{ + private void M(ReadOnlySpan data) + { + var container = new Container { Data = data }; + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + [Fact] + public async Task SpanParameter_MultipleReferencesOneWrite_NoDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Span data) + { + Console.WriteLine(data.Length); + Console.WriteLine(data[0]); + data[1] = 5; // Write + Console.WriteLine(data[2]); + } +} +"; + await VerifyCSCodeFixAsync(source, source); + } + + [Fact] + public async Task MemoryParameter_PassedToMethodExpectingReadOnly_ProducesDiagnostic() + { + string source = @" +using System; + +class C +{ + private void M(Memory [|data|]) + { + Helper(data); + } + + private void Helper(ReadOnlyMemory data) + { + Console.WriteLine(data.Length); + } +} +"; + string expected = @" +using System; + +class C +{ + private void M(ReadOnlyMemory data) + { + Helper(data); + } + + private void Helper(ReadOnlyMemory data) + { + Console.WriteLine(data.Length); + } +} +"; + await VerifyCSCodeFixAsync(source, expected); + } + + private static async Task VerifyCSCodeFixAsync(string source, string fixedSource) + { + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_PassedAsRefArgument_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + private static void IntroSort(Span 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; + } + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_RefVariableDeclaration_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + private void Method(Span data) + { + // Taking a ref to an indexed element requires writability + ref int firstElement = ref data[0]; + firstElement = 42; + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_RefReturn_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + // Method returns ref, so parameter must be writable + private ref int GetFirst(Span data) + { + return ref data[0]; + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task MemoryParameter_PassedToMethodViaSlice_NoDiagnostic() + { + var source = """ + using System; + using System.Threading; + using System.Threading.Tasks; + + class Test + { + // buffer is passed via Slice to a method that expects writable Memory + internal async ValueTask ReadBlockAsyncInternal(Memory 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 ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + { + // Implementation that writes to buffer + return ValueTask.FromResult(buffer.Length); + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_UsedInFixed_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + private unsafe void ProcessData(Span data) + { + fixed (byte* ptr = data) + { + // Use pointer + *ptr = 42; + } + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + SolutionTransforms = + { + (solution, projectId) => solution.WithProjectCompilationOptions(projectId, + ((CSharpCompilationOptions)solution.GetProject(projectId)!.CompilationOptions!).WithAllowUnsafe(true)) + } + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_IndexerWithDecrementOperator_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + private void DecrementLast(Span buffer) + { + int length = buffer.Length; + buffer[length - 1]--; + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_SliceAssignedToLocalAndWritten_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + internal static ReadOnlySpan CopyRuntimeTypeHandles(int[] inHandles, Span stackScratch) + { + if (inHandles == null || inHandles.Length == 0) + { + return default; + } + + Span 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; + } + } + """; + + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SpanParameter_ChainedSliceWithIncrementOperator_NoDiagnostic() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System; + + class Test + { + private void Method(Span span) + { + span.Slice(1, 4).Slice(1, 2)[0]++; + } + } + """); + } + } +}