From 2f82379cbe1a75e87cc968a5bec08165dbd8a7b1 Mon Sep 17 00:00:00 2001 From: Martin Taillefer Date: Wed, 4 Jan 2023 18:59:23 -0800 Subject: [PATCH] Add the UseConreteType analyzer (#6370) * Add the UseConreteType analyzer As per dotnet/runtime#51193. This recommends using concrete types for fields, local, parameters, and method returns instead of interface/abstract types when possible and when it doesn't affect the API surface of a class. The idea is to help eliminate virtual/interface dispatch, while also making available potentially richer APIs exposed by implementations relative to their interface. * Updates Co-authored-by: Martin Taillefer --- .../Core/AnalyzerReleases.Unshipped.md | 1 + .../MicrosoftNetCoreAnalyzersResources.resx | 18 + .../UseConcreteTypeAnalyzer.Collector.cs | 470 ++++++++++++ .../Performance/UseConcreteTypeAnalyzer.cs | 216 ++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 30 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 30 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 30 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 30 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 30 + .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + src/NetAnalyzers/RulesMissingDocumentation.md | 1 + .../UseConcreteTypeTests.Disqualification.cs | 286 +++++++ .../Performance/UseConcreteTypeTests.cs | 720 ++++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- 23 files changed, 2135 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.Disqualification.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 535ba85922..3f1647c0f9 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -11,6 +11,7 @@ CA1513 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](http CA1856 | Performance | Error | ConstantExpectedAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1856) CA1857 | Performance | Warning | ConstantExpectedAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857) CA1858 | Performance | Info | UseStartsWithInsteadOfIndexOfComparisonWithZero, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858) +CA1859 | Performance | Info | UseConcreteTypeAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 50fc967554..c85a352e35 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1980,4 +1980,22 @@ Use '{0}.{1}' + + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + Use concrete types when possible for improved performance + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs new file mode 100644 index 0000000000..632a38fd2e --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs @@ -0,0 +1,470 @@ +// 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.Generic; +using System.Linq; +using System.Threading; +using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.PooledObjects; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + public partial class UseConcreteTypeAnalyzer + { + private sealed class Collector + { + private static readonly ObjectPool _pool = new(() => new Collector()); + + public ConcurrentDictionary VirtualDispatchFields { get; } = new(SymbolEqualityComparer.Default); + public ConcurrentDictionary VirtualDispatchLocals { get; } = new(SymbolEqualityComparer.Default); + public ConcurrentDictionary VirtualDispatchParameters { get; } = new(SymbolEqualityComparer.Default); + public ConcurrentDictionary MethodsAssignedToDelegate { get; } = new(SymbolEqualityComparer.Default); + + public ConcurrentDictionary> FieldAssignments { get; } = new(SymbolEqualityComparer.Default); + public ConcurrentDictionary> LocalAssignments { get; } = new(SymbolEqualityComparer.Default); + public ConcurrentDictionary> ParameterAssignments { get; } = new(SymbolEqualityComparer.Default); + public ConcurrentDictionary> MethodReturns { get; } = new(SymbolEqualityComparer.Default); + + public INamedTypeSymbol? Void { get; private set; } + + private Collector() + { + } + + private void Reset() + { + VirtualDispatchFields.Clear(); + VirtualDispatchLocals.Clear(); + VirtualDispatchParameters.Clear(); + MethodsAssignedToDelegate.Clear(); + + DrainDictionary(FieldAssignments); + DrainDictionary(LocalAssignments); + DrainDictionary(ParameterAssignments); + DrainDictionary(MethodReturns); + + Void = null; + + static void DrainDictionary(ConcurrentDictionary> d) + { + foreach (var kvp in d) + { + kvp.Value.Dispose(); + } + + d.Clear(); + } + } + + public static Collector GetInstance(Compilation compilation) + { + var c = _pool.Allocate(); + c.Void = compilation.GetSpecialType(SpecialType.System_Void); + return c; + } + + public static void ReturnInstance(Collector c, CancellationToken cancellationToken) + { + c.Reset(); + _pool.Free(c, cancellationToken); + } + + /// + /// Identify fields/locals/params that are used as 'this' for a virtual dispatch. + /// + public void HandleInvocation(IInvocationOperation op) + { + if (op.IsVirtual) + { + if (op.Instance != null) + { + var instance = op.Instance; + if (instance.Kind == OperationKind.ConditionalAccessInstance) + { + var parent = ((IConditionalAccessInstanceOperation)instance).GetConditionalAccess() ?? instance; + if (parent != null) + { + instance = ((IConditionalAccessOperation)parent).Operation; + } + } + + switch (instance.Kind) + { + case OperationKind.FieldReference: + { + var fieldRef = (IFieldReferenceOperation)instance; + if (CanUpgrade(fieldRef.Field)) + { + VirtualDispatchFields[fieldRef.Field] = true; + } + + break; + } + + case OperationKind.ParameterReference: + { + var parameterRef = (IParameterReferenceOperation)instance; + VirtualDispatchParameters[parameterRef.Parameter] = true; + break; + } + + case OperationKind.LocalReference: + { + var localRef = (ILocalReferenceOperation)instance; + VirtualDispatchLocals[localRef.Local] = true; + break; + } + } + } + } + + bool canUpgrade = CanUpgrade(op.TargetMethod); + + foreach (var arg in op.Arguments) + { + if (arg.Value is IDelegateCreationOperation delegateOp) + { + if (delegateOp.Target is IMethodReferenceOperation methodRefOp) + { + MethodsAssignedToDelegate[methodRefOp.Method] = true; + } + } + + if (CanUpgrade(arg.Value)) + { + if (arg.Parameter != null) + { + if (arg.Parameter.RefKind is RefKind.Ref or RefKind.Out) + { + RecordAssignment(arg.Value, arg.Parameter.Type); + } + + if (canUpgrade) + { + var valueTypes = GetValueTypes(arg.Value); + foreach (var valueType in valueTypes) + { + RecordAssignment(arg.Parameter, valueType); + } + } + } + } + } + } + + /// + /// Record the type of values assigned to each field/local/param. + /// + public void HandleSimpleAssignment(ISimpleAssignmentOperation op) + { + if (CanUpgrade(op.Target)) + { + var valueTypes = GetValueTypes(op.Value); + RecordAssignment(op.Target, valueTypes); + } + } + + /// + /// Record the type of values assigned to each field/local/param. + /// + public void HandleCoalesceAssignment(ICoalesceAssignmentOperation op) + { + if (CanUpgrade(op.Target)) + { + var valueTypes = GetValueTypes(op.Value); + RecordAssignment(op.Target, valueTypes); + } + } + + /// + /// Record the type of values assigned to each field/local/param. + /// + public void HandleDeconstructionAssignment(IDeconstructionAssignmentOperation op) + { + var tupleTypes = GetValueTypes(op.Value); + foreach (var tupleType in tupleTypes.OfType()) + { + for (int i = 0; i < tupleType.TypeArguments.Length; i++) + { + var valueType = tupleType.TypeArguments[i]; + if (valueType != null) + { + switch (op.Target.Kind) + { + case OperationKind.Tuple: + { + var tupleOp = (ITupleOperation)op.Target; + if (CanUpgrade(tupleOp.Elements[i])) + { + RecordAssignment(tupleOp.Elements[i], valueType); + } + + break; + } + + case OperationKind.DeclarationExpression: + { + var declOp = (IDeclarationExpressionOperation)op.Target; + if (declOp.Expression is ITupleOperation tupleOp) + { + if (CanUpgrade(tupleOp.Elements[i])) + { + RecordAssignment(tupleOp.Elements[i], valueType); + } + } + + break; + } + } + } + } + } + } + + /// + /// Record the type of values used to initialize fields. + /// + public void HandleFieldInitializer(IFieldInitializerOperation op) + { + if (CanUpgrade(op)) + { + var valueTypes = GetValueTypes(op.Value); + foreach (var valueType in valueTypes) + { + foreach (var field in op.InitializedFields) + { + if (CanUpgrade(field)) + { + RecordAssignment(field, valueType); + } + } + } + } + } + + /// + /// Record the type of values used to initialize locals. + /// + public void HandleVariableDeclarator(IVariableDeclaratorOperation op) + { + if (op.Initializer != null && CanUpgrade(op.Initializer)) + { + var valueTypes = GetValueTypes(op.Initializer.Value); + foreach (var valueType in valueTypes) + { + RecordAssignment(op.Symbol, valueType); + } + } + } + + /// + /// Record the type of values used to initialize locals via an expression. + /// + public void HandleDeclarationExpression(IDeclarationExpressionOperation op) + { + if (op.Expression != null && CanUpgrade(op.Expression)) + { + if (op.Expression.Kind == OperationKind.LocalReference) + { + var localRef = (ILocalReferenceOperation)op.Expression; + if (localRef.Type != null) + { + var set = LocalAssignments.GetOrAdd(localRef.Local, _ => PooledConcurrentSet.GetInstance(SymbolEqualityComparer.Default)); + set.Add(localRef.Type); + } + } + } + } + + /// + /// Record the type of values returned by a given method. + /// + public void HandleReturn(IReturnOperation op) + { + if (op.ReturnedValue != null) + { + if (op.SemanticModel!.GetEnclosingSymbol(op.Syntax.SpanStart) is IMethodSymbol methodSym && CanUpgrade(methodSym)) + { + var valueTypes = GetValueTypes(op.ReturnedValue); + foreach (var valueType in valueTypes) + { + RecordAssignment(methodSym, valueType); + } + } + } + } + + /// + /// Trivial reject for types that can't be upgraded in order to avoid wasted work. + /// + private static bool CanUpgrade(IOperation target) + => target.Type == null || (!target.Type.IsSealed && !target.Type.IsValueType); + + /// + /// Trivial reject for methods that can't be upgraded in order to avoid wasted work. + /// + private static bool CanUpgrade(IMethodSymbol methodSym) + => methodSym.DeclaredAccessibility == Accessibility.Private && methodSym.MethodKind == MethodKind.Ordinary; + + /// + /// Trivial reject for fields that can't be upgraded in order to avoid wasted work. + /// + private static bool CanUpgrade(IFieldSymbol fieldSym) + => fieldSym.DeclaredAccessibility == Accessibility.Private; + + private List GetValueTypes(IOperation op) + { + var values = new List(); + GetValueTypes(values, op); + return values; + } + + private void GetValueTypes(List values, IOperation op) + { + switch (op.Kind) + { + case OperationKind.Literal: + { + if (op.HasNullConstantValue()) + { + // use 'void' as a marker for a null assignment + values.Add(Void!); + } + + return; + } + + case OperationKind.Conversion: + { + var convOp = (IConversionOperation)op; + GetValueTypes(values, convOp.Operand); + return; + } + + case OperationKind.ConditionalAccess: + { + var condOp = (IConditionalAccessOperation)op; + GetValueTypes(values, condOp.WhenNotNull); + return; + } + + case OperationKind.Conditional: + { + var condOp = (IConditionalOperation)op; + GetValueTypes(values, condOp.WhenTrue); + GetValueTypes(values, condOp.WhenFalse); + return; + } + + case OperationKind.Coalesce: + { + var colOp = (ICoalesceOperation)op; + GetValueTypes(values, colOp.Value); + GetValueTypes(values, colOp.WhenNull); + return; + } + + case OperationKind.Invocation: + case OperationKind.ArrayElementReference: + case OperationKind.ObjectCreation: + case OperationKind.ParameterReference: + case OperationKind.PropertyReference: + case OperationKind.MethodReference: + case OperationKind.LocalReference: + { + if (op.Type != null) + { + values.Add(op.Type!); + } + + return; + } + + case OperationKind.FieldReference: + { + var fieldRefOp = (IFieldReferenceOperation)op; + if (CanUpgrade(fieldRefOp.Field)) + { + values.Add(op.Type!); + } + + return; + } + + case OperationKind.DelegateCreation: + { + var delOp = (IDelegateCreationOperation)op; + if (delOp.Target is IMethodReferenceOperation target) + { + MethodsAssignedToDelegate[target.Method] = true; + } + + return; + } + + case OperationKind.InstanceReference: + { + var instRef = (IInstanceReferenceOperation)op; + values.Add(instRef.Type!); + return; + } + } + } + + private void RecordAssignment(IOperation op, List valueTypes) + { + foreach (var valueType in valueTypes) + { + RecordAssignment(op, valueType); + } + } + + private void RecordAssignment(IOperation op, ITypeSymbol valueType) + { + switch (op.Kind) + { + case OperationKind.FieldReference: + { + var fieldRef = (IFieldReferenceOperation)op; + + // only consider fields that are being compiled, not fields from imported types + if (fieldRef.Field.DeclaringSyntaxReferences.Length > 0) + { + RecordAssignment(fieldRef.Field, valueType); + } + + break; + } + + case OperationKind.LocalReference: + { + var localRef = (ILocalReferenceOperation)op; + RecordAssignment(localRef.Local, valueType); + break; + } + + case OperationKind.ParameterReference: + { + var paramRef = (IParameterReferenceOperation)op; + RecordAssignment(paramRef.Parameter, valueType); + break; + } + + case OperationKind.DeclarationExpression: + { + var declEx = (IDeclarationExpressionOperation)op; + RecordAssignment(declEx.Expression, valueType); + break; + } + } + } + + private void RecordAssignment(IFieldSymbol field, ITypeSymbol valueType) => FieldAssignments.GetOrAdd(field, _ => PooledConcurrentSet.GetInstance(SymbolEqualityComparer.Default)).Add(valueType); + private void RecordAssignment(ILocalSymbol local, ITypeSymbol valueType) => LocalAssignments.GetOrAdd(local, _ => PooledConcurrentSet.GetInstance(SymbolEqualityComparer.Default)).Add(valueType); + private void RecordAssignment(IParameterSymbol parameter, ITypeSymbol valueType) => ParameterAssignments.GetOrAdd(parameter.OriginalDefinition, _ => PooledConcurrentSet.GetInstance(SymbolEqualityComparer.Default)).Add(valueType); + private void RecordAssignment(IMethodSymbol method, ITypeSymbol valueType) => MethodReturns.GetOrAdd(method, _ => PooledConcurrentSet.GetInstance(SymbolEqualityComparer.Default)).Add(valueType); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs new file mode 100644 index 0000000000..7b6b9fa1dd --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs @@ -0,0 +1,216 @@ +// 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.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.Lightup; +using Analyzer.Utilities.PooledObjects; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + // Ideas for the future + // ==================== + // Detect arrays/collections of interface types which could be replaced with arrays/collections of concrete types + // Suggest to upgrade members of tuples returned from a method + // only suggest a replacement type if it reduces the number of virtual/interface calls + + /// + /// Identifies locals/fields/parameters/return types which can be switched to a concrete type to eliminate virtual/interface dispatch. + /// + /// + /// First, we collect a bunch of state: + /// + /// * For all locals/fields/parameters/returns within the named type, we create bags representing the types having been assigned to each. + /// This state will be used to know if we can 'upgrade' the element's type. + /// + /// * For all locals/fields/parameters within the named type, we keep track of when they are used as 'this' for a virtual/interface call. + /// This state will be used to filter out diagnostics for those locals/fields/parameters which aren't inducing virtual/interface calls. + /// There's no sense in upgrading those elements if they aren't the source of virtual/interface calls. + /// + /// * We keep track of all methods assigned to delegates so that we don't suggest changing the signature of these methods. + /// + /// Once all this state has been collected, we perform the actual analysis: + /// + /// * Based on the bags of types being assigned to each local/field/parameter, if there is only one type being assigned and this + /// type is more specialized than what the element's type is, then we suggest upgrading the element's type accordingly. + /// + /// * Based on the bags of types being returned by each method, if there is only one type being returned and this type is more specialized + /// than what was there before, then we suggest upgrading the return type accordingly. + /// + /// Several constraints are applied before we suggest modifying a method signature (either one of its parameters or its return type): + /// + /// * The method cannot be implementing any interface. + /// + /// * The method cannot be virtual, abstract, or be an override. + /// + /// * The method must be private. + /// + /// * The method must not have been assigned to a delegate. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed partial class UseConcreteTypeAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1859"; + + internal static readonly DiagnosticDescriptor UseConcreteTypeForMethodReturn = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(UseConcreteTypeTitle)), + CreateLocalizableResourceString(nameof(UseConcreteTypeForMethodReturnMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(UseConcreteTypeDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor UseConcreteTypeForParameter = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(UseConcreteTypeTitle)), + CreateLocalizableResourceString(nameof(UseConcreteTypeForParameterMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(UseConcreteTypeDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor UseConcreteTypeForLocal = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(UseConcreteTypeTitle)), + CreateLocalizableResourceString(nameof(UseConcreteTypeForLocalMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(UseConcreteTypeDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor UseConcreteTypeForField = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(UseConcreteTypeTitle)), + CreateLocalizableResourceString(nameof(UseConcreteTypeForFieldMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(UseConcreteTypeDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + UseConcreteTypeForField, + UseConcreteTypeForLocal, + UseConcreteTypeForMethodReturn, + UseConcreteTypeForParameter); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterSymbolStartAction(context => + { + var coll = Collector.GetInstance(context.Compilation); + + context.RegisterOperationAction(context => coll.HandleInvocation((IInvocationOperation)context.Operation), OperationKind.Invocation); + context.RegisterOperationAction(context => coll.HandleSimpleAssignment((ISimpleAssignmentOperation)context.Operation), OperationKind.SimpleAssignment); + context.RegisterOperationAction(context => coll.HandleCoalesceAssignment((ICoalesceAssignmentOperation)context.Operation), OperationKind.CoalesceAssignment); + context.RegisterOperationAction(context => coll.HandleDeconstructionAssignment((IDeconstructionAssignmentOperation)context.Operation), OperationKind.DeconstructionAssignment); + context.RegisterOperationAction(context => coll.HandleFieldInitializer((IFieldInitializerOperation)context.Operation), OperationKind.FieldInitializer); + context.RegisterOperationAction(context => coll.HandleVariableDeclarator((IVariableDeclaratorOperation)context.Operation), OperationKind.VariableDeclarator); + context.RegisterOperationAction(context => coll.HandleDeclarationExpression((IDeclarationExpressionOperation)context.Operation), OperationKind.DeclarationExpression); + context.RegisterOperationAction(context => coll.HandleReturn((IReturnOperation)context.Operation), OperationKind.Return); + + context.RegisterSymbolEndAction(context => + { + Report(context, coll); + Collector.ReturnInstance(coll, context.CancellationToken); + }); + }, SymbolKind.NamedType); + } + + /// + /// Given all the accumulated analysis state, generate the diagnostics. + /// + private static void Report(SymbolAnalysisContext context, Collector coll) + { + foreach (var field in coll.VirtualDispatchFields.Keys) + { + if (coll.FieldAssignments.TryGetValue(field, out var assignments)) + { + Report(field, field.Type, assignments, UseConcreteTypeForField); + } + } + + foreach (var local in coll.VirtualDispatchLocals.Keys) + { + if (coll.LocalAssignments.TryGetValue(local, out var assignments)) + { + Report(local, local.Type, assignments, UseConcreteTypeForLocal); + } + } + + foreach (var parameter in coll.VirtualDispatchParameters.Keys) + { + if (coll.ParameterAssignments.TryGetValue(parameter, out var assignments)) + { + if (parameter.ContainingSymbol is IMethodSymbol method) + { + if (CanUpgrade(method)) + { + Report(parameter, parameter.Type, assignments, UseConcreteTypeForParameter); + } + } + } + } + + foreach (var pair in coll.MethodReturns) + { + var method = pair.Key; + var returns = pair.Value; + + if (CanUpgrade(method)) + { + Report(method, method.ReturnType, returns, UseConcreteTypeForMethodReturn); + } + } + + void Report(ISymbol sym, ITypeSymbol fromType, PooledConcurrentSet assignments, DiagnosticDescriptor desc) + { + using var types = PooledHashSet.GetInstance(assignments, SymbolEqualityComparer.Default); + + var assignedNull = types.Remove(coll.Void!); + + if (types.Count == 1) + { + var toType = types.Single(); + if (assignedNull) + { + toType = toType.WithNullableAnnotation(Analyzer.Utilities.Lightup.NullableAnnotation.Annotated); + } + + if (!toType.DerivesFrom(fromType.OriginalDefinition)) + { + return; + } + + if (toType.TypeKind == TypeKind.Class + && !SymbolEqualityComparer.Default.Equals(fromType, toType) + && toType.SpecialType != SpecialType.System_Object + && toType.SpecialType != SpecialType.System_Delegate) + { + var fromTypeName = GetTypeName(fromType); + var toTypeName = GetTypeName(toType); + var diagnostic = sym.CreateDiagnostic(desc, sym.Name, fromTypeName, toTypeName); + context.ReportDiagnostic(diagnostic); + } + } + } + + bool CanUpgrade(IMethodSymbol methodSym) => !coll.MethodsAssignedToDelegate.ContainsKey(methodSym); + + static string GetTypeName(ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 1e5f15df3a..63cd76a199 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2662,6 +2662,36 @@ Použijte ThrowIfCancellationRequested + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Použít zásady přístupu na úrovni kontejneru diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index c0d8e53584..b21afe4203 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2662,6 +2662,36 @@ "ThrowIfCancellationRequested()" verwenden + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Zugriffsrichtlinie auf Containerebene verwenden diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 6c7b83e11b..8eff9a74c8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2662,6 +2662,36 @@ Usar "ThrowIfCancellationRequested" + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Usar una directiva de acceso de nivel de contenedor diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 3fd15242c7..4e90f3277d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2662,6 +2662,36 @@ Appeler ThrowIfCancellationRequested() + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Utiliser une stratégie d'accès au niveau du conteneur diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index b0f39a886f..46929cf5fa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2662,6 +2662,36 @@ Usare 'ThrowIfCancellationRequested' + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Usa criteri di accesso a livello di contenitore diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index a1a04371f6..7f89a82497 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2662,6 +2662,36 @@ 'ThrowIfCancellationRequested' を呼び出す + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy コンテナー レベルのアクセス ポリシーを使用する diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 104a0d52f4..9df812706f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2662,6 +2662,36 @@ 'ThrowIfCancellationRequested' 사용 + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy 컨테이너 수준 액세스 정책 사용 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 3e8b83ebf6..1b4fafd31d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2662,6 +2662,36 @@ Użyj polecenia „ThrowIfCancellationRequested” + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Użyj zasad dostępu na poziomie kontenera diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 1104342d4e..9fa1bdee82 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2662,6 +2662,36 @@ Use 'ThrowIfCancellationRequested' + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Usar Política de Acesso no Nível de Contêiner diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index ed599f2270..02e39eff91 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2662,6 +2662,36 @@ Использовать "ThrowIfCancellationRequested" + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Использовать политику доступа на уровне контейнера diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 6ef6c178a4..2b621bea6f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2662,6 +2662,36 @@ 'ThrowIfCancellationRequested' kullanın + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy Kapsayıcı Düzeyinde Erişim İlkesi Kullan diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 236037bcdc..d1a5f5ed81 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2662,6 +2662,36 @@ 使用 “ThrowIfCancellationRequested” + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy 使用容器级别访问策略 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index b85c3f1765..2b3c8b3806 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2662,6 +2662,36 @@ 請使用 'ThrowIfCancellationRequested' + + Using concrete types avoids virtual or interface call overhead and enables inlining. + Using concrete types avoids virtual or interface call overhead and enables inlining. + + + + Change type of field '{0}' from '{1}' to '{2}' for improved performance + Change type of field '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + Change type of variable '{0}' from '{1}' to '{2}' for improved performance + + + + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + Change return type of method '{0}' from '{1}' to '{2}' for improved performance + + + + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + Change type of parameter '{0}' from '{1}' to '{2}' for improved performance + + + + Use concrete types when possible for improved performance + Use concrete types when possible for improved performance + + Use Container Level Access Policy 使用容器層級存取原則 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 7a51e0bf08..29d6af40b3 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1668,6 +1668,18 @@ It is both clearer and faster to use 'StartsWith' instead of comparing the resul |CodeFix|True| --- +## [CA1859](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859): Use concrete types when possible for improved performance + +Using concrete types avoids virtual or interface call overhead and enables inlining. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|False| +--- + ## [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/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index b137230d32..416275fda2 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3095,6 +3095,26 @@ ] } }, + "CA1859": { + "id": "CA1859", + "shortDescription": "Use concrete types when possible for improved performance", + "fullDescription": "Using concrete types avoids virtual or interface call overhead and enables inlining.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "UseConcreteTypeAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 6d742e2829..7325b31a64 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -11,3 +11,4 @@ CA1513 | | Incorrect usage of ConstantExpected attribute | CA1857 | | A constant is expected for the parameter | CA1858 | | Use 'StartsWith' instead of 'IndexOf' | +CA1859 | | Use concrete types when possible for improved performance | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.Disqualification.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.Disqualification.cs new file mode 100644 index 0000000000..255d819bdb --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.Disqualification.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public static partial class UseConcreteTypeTests + { + [Theory] + [MemberData(nameof(DisqualifiedSources))] + public static async Task Disqualication(string insert) + { + string source = @$" + #pragma warning disable CS8019 + #nullable enable + + using System; + using System.Threading.Tasks; + + namespace Example + {{ + public interface IFoo + {{ + void Bar(); + }} + + public class Foo : IFoo + {{ + public void Bar() + {{ + }} + }} + + {insert} + }}"; + + await TestCSAsync(source); + } + + public static IEnumerable DisqualifiedSources => new List + { + new[] + { + @" + public class TestLocalFunction + { + private IFoo SyncMethod(int x) + { + switch (x) + { + case 0: return MakeFoo(); + default: return new Foo(); + } + + static IFoo MakeFoo() => new Foo() as IFoo; + } + + private async Task AsyncMethod(Task stuff, int x) + { + await stuff; + + switch (x) + { + case 0: return MakeFoo(); + default: return new Foo(); + } + + static IFoo MakeFoo() => new Foo() as IFoo; + } + + private async Task TryCatchAsyncMethod(Task stuff, int x) + { + try + { + await stuff; + return new Foo(); + } + catch + { + return new Foo(); + } + } + } + ", + }, + + new[] + { + @" + public class TestArray + { + private readonly IFoo[] _foos = new IFoo[3]; + + private IFoo Method(int x) + { + switch (x) + { + case 0: return _foos[0]; + default: return new Foo(); + } + } + } + ", + }, + + new[] + { + @" + public class TestLambda + { + private IFoo MethodWithExpression(int x) + { + switch (x) + { + case 0: return Stub(() => new Foo()); + default: return new Foo(); + } + } + + private IFoo MethodWithBlock(int x) + { + switch (x) + { + case 0: + return Stub(() => + { + return new Foo(); + }); + default: return new Foo(); + } + } + + public IFoo Stub(Func func) + { + return func(); + } + } + ", + }, + + new[] + { + @" + public class TestByRef + { + private IFoo MethodUsingByRef(int x) + { + switch (x) + { + case 0: + { + IFoo localRef = new Foo(); + RefMethod(ref localRef); + return localRef; + } + + case 1: + { + OutMethod(out var localOut); + return localOut; + } + + default: + return new Foo(); + } + } + + public void RefMethod(ref IFoo foo) + { + foo = new Foo(); + } + + public void OutMethod(out IFoo foo) + { + foo = new Foo(); + } + } + ", + }, + + new[] + { + @" + public class TestTuples + { + private IFoo MethodTuple(int x) + { + switch (x) + { + case 0: + var (l, m) = MakeTuple(); + return l; + + default: return new Foo(); + } + } + + public (IFoo, IFoo) MakeTuple() + { + return (new Foo(), new Foo()); + } + } + ", + }, + + new[] + { + @" + public interface IBase + { + IFoo Method(); + } + + public class TestInterfaceMethod : IBase + { + public IFoo Method() + { + return new Foo(); + } + } + ", + }, + + new[] + { + @" + public class Base1 + { + public virtual IFoo Method() + { + return new Foo(); + } + } + + public class Base2 : Base1 + { + } + + public class TestOverrideMethod : Base2 + { + public override IFoo Method() + { + return new Foo(); + } + } + ", + }, + + new[] + { + @" + public abstract class TestConstraints + { + public virtual IFoo VirtualMethod() =>new Foo(); + public abstract IFoo AbstractMethod(); + public IFoo PublicMethod() => new Foo(); + public IFoo InternalMethod() => new Foo(); + } + ", + }, + + new[] + { + @" + public class TestConflictingLocals + { + public void Test(int x) + { + IFoo l; + + switch (x) + { + case 0: l = new Foo(); break; + case 1: l = MakeFoo(); break; + } + } + + public IFoo MakeFoo() => new Foo(); + } + ", + }, + }; + } +} diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.cs new file mode 100644 index 0000000000..9f089ec666 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeTests.cs @@ -0,0 +1,720 @@ +// 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.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.UseConcreteTypeAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public static partial class UseConcreteTypeTests + { + [Fact] + public static async Task ShouldNotTrigger1() + { + const string Source = @" + #nullable enable + + using System; + using System.Collections.Generic; + + namespace Example + { + public class BaseType + { + } + + public class Derived1 : BaseType + { + } + + public class Derived2 : BaseType + { + private BaseType? Foo(int x) + { + if (x == 0) return null; + if (x == 1) return new Derived1(); + + return this; + } + } + }"; + + await TestCSAsync(Source); + } + + [Fact] + public static async Task ShouldTrigger1() + { + const string Source = @" + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() { } + } + + public static class Tester + { + private static void Do(IFoo {|#0:foo|}) + { + foo.Bar(); + } + + private static void MakeCall() + { + Do(new Foo()); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForParameter) + .WithLocation(0) + .WithArguments("foo", "Example.IFoo", "Example.Foo")); + } + + [Fact] + public static async Task ShouldTrigger2() + { + const string Source = @" + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() { } + } + + public static class Tester + { + private static void Do(IFoo {|#0:foo|}) + { + foo.Bar(); + } + + private static void MakeCall() + { + Do(new Foo()); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForParameter) + .WithLocation(0) + .WithArguments("foo", "Example.IFoo", "Example.Foo")); + } + + [Fact] + public static async Task Params() + { + const string Source = @" + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class Test + { + private void Method(IFoo {|#0:foo|}) + { + foo.Bar(); + } + + private void Method2(IFoo foo) + { + foo.Bar(); + } + + private void Caller(IFoo ifoo) + { + Method(new Foo()); + Method2(new Foo()); + Method2(ifoo); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForParameter) + .WithLocation(0) + .WithArguments("foo", "Example.IFoo", "Example.Foo")); + } + + [Fact] + public static async Task Conditional() + { + const string Source = @" +#nullable enable + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class FooProvider + { + public Foo Foo { get { return new Foo(); } } + } + + public class AttributeData + { + public SyntaxReference? ApplicationReference { get; } + } + + public abstract class SyntaxReference + { + public abstract SyntaxNode GetSyntax(); + } + + public abstract class SyntaxNode + { + public Location GetLocation() => new(); + } + + public class Location + { + } + + public class Test + { + private IFoo? {|#0:Method1|}(FooProvider? provider) + { + return provider?.Foo; + } + + private void Method2() + { + AttributeData attr = new(); + _ = attr.ApplicationReference?.GetSyntax().GetLocation(); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForMethodReturn) + .WithLocation(0) + .WithArguments("Method1", "Example.IFoo?", "Example.Foo")); + } + + [Fact] + public static async Task Tuples() + { + const string Source = @" + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class Test + { + private IFoo {|#0:MethodTuple|}(int x) + { + switch (x) + { + case 0: + { + Foo l; IFoo m; + (l, m) = MakeTuple(); + return l; + } + + case 1: + { + var (l, m) = MakeTuple(); + return l; + } + + default: return new Foo(); + } + } + + public (Foo, IFoo) MakeTuple() + { + return (new Foo(), new Foo()); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForMethodReturn) + .WithLocation(0) + .WithArguments("MethodTuple", "Example.IFoo", "Example.Foo")); + } + + [Fact] + public static async Task Locals() + { + const string Source = @" +#nullable enable + using System; + + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class Test + { + private static readonly Foo _fooField = new(); + private Foo FooMethod() => new Foo(); + private void FooRefMethod(ref Foo x) { } + private void FooOutMethod(out Foo x) { x = new Foo(); } + private Foo FooProp { get { return _fooField; } } + + private static readonly IFoo _ifooField = (IFoo)new Foo(); + private IFoo IFooMethod() => _ifooField; + private void IFooRefMethod(ref IFoo x) { } + private void IFooOutMethod(out IFoo x) { x = new Foo(); } + private IFoo IFooProp { get { return _ifooField; } } + + public void Method(int x, Foo fooParam, IFoo ifooParam) + { + Foo fooLocal = new Foo(); + Foo[] fooArray = new Foo[0]; + Func fooDelegate = FooMethod; + + IFoo ifooLocal = new Foo(); + IFoo[] ifooArray = new IFoo[0]; + Func ifooDelegate = IFooMethod; + + IFoo? {|#0:l0|} = null; + IFoo? l1 = new Foo(); + IFoo? l2 = new Foo(); + IFoo? l3 = new Foo(); + IFoo? l4 = new Foo(); + IFoo? l5 = new Foo(); + IFoo? l6 = new Foo(); + IFoo? l7 = new Foo(); + IFoo? l8 = new Foo(); + IFoo? l9 = new Foo(); + + switch (x) + { + case 0: l0 = new Foo(); break; + case 1: l0 = null; break; + case 2: l0 = null!; break; + case 3: l0 = _fooField; break; + case 4: l0 = fooArray[0]; break; + case 5: l0 = FooProp; break; + case 6: l0 = fooLocal; break; + case 7: l0 = fooParam; break; + case 8: l0 = FooMethod(); break; + case 9: l0 = fooDelegate(); break; + } + + l1 = ifooLocal; + l2 = _ifooField; + l3 = ifooArray[0]; + l4 = IFooProp; + l5 = IFooMethod(); + l6 = ifooParam; + l7 = ifooDelegate(); + IFooRefMethod(ref l8); + IFooOutMethod(out l9); + + // induce virtual calls to trigger the diags + l0?.Bar(); + l1?.Bar(); + l2?.Bar(); + l3?.Bar(); + l4?.Bar(); + l5?.Bar(); + l6?.Bar(); + l7?.Bar(); + l8?.Bar(); + l9?.Bar(); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(0) + .WithArguments("l0", "Example.IFoo?", "Example.Foo?")); + } + + [Fact] + public static async Task Complex() + { + const string Source = @" +#pragma warning disable CS0619 + + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class Test + { + IFoo {|#0:f0|} = MakeFoo(); + IFoo {|#1:f1|} = new Foo(); + IFoo {|#2:f2|}; + IFoo {|#3:f3|}; + IFoo {|#4:f4|}; + IFoo {|#5:f5|}; + IFoo {|#6:f6|}; + + public Test(int x) + { + f2 = MakeFoo(); + f3 = (x == 0) ? new Foo() : MakeFoo(); + f4 = new Foo(); + f5 ??= MakeFoo(); + f6 = MakeFoo() ?? MakeFoo(); + } + + public void M(int x) + { + IFoo {|#7:l0|} = MakeFoo(); + IFoo {|#8:l1|} = (x == 0) ? new Foo() : MakeFoo(); + IFoo {|#9:l2|} = new Foo(); + IFoo {|#10:l3|}; l3 = MakeFoo(); + IFoo {|#11:l4|}; l4 = (x == 0) ? new Foo() : MakeFoo(); + IFoo {|#12:l5|}; l5 = new Foo(); + IFoo {|#13:l6|}; l6 = null; l6 ??= MakeFoo(); + IFoo {|#14:l7|}; l7 = MakeFoo() ?? MakeFoo(); + + // make virtual calls so the analyzer will trigger + l0.Bar(); + l1.Bar(); + l2.Bar(); + l3.Bar(); + l4.Bar(); + l5.Bar(); + l6.Bar(); + l7.Bar(); + f0.Bar(); + f1.Bar(); + f2.Bar(); + f3.Bar(); + f4.Bar(); + f5.Bar(); + f6.Bar(); + } + + static Foo MakeFoo() => new Foo(); + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(0) + .WithArguments("f0", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(1) + .WithArguments("f1", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(2) + .WithArguments("f2", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(3) + .WithArguments("f3", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(4) + .WithArguments("f4", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(5) + .WithArguments("f5", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(6) + .WithArguments("f6", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(7) + .WithArguments("l0", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(8) + .WithArguments("l1", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(9) + .WithArguments("l2", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(10) + .WithArguments("l3", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(11) + .WithArguments("l4", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(12) + .WithArguments("l5", "Example.IFoo", "Example.Foo"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(13) + .WithArguments("l6", "Example.IFoo", "Example.Foo?"), + + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForLocal) + .WithLocation(14) + .WithArguments("l7", "Example.IFoo", "Example.Foo")); + } + + [Fact] + public static async Task Fields() + { + const string Source = @" +#nullable enable + using System; + + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class Test + { + private static readonly Foo _fooField = new(); + private Foo FooMethod() => new Foo(); + private void FooRefMethod(ref Foo x) { } + private void FooOutMethod(out Foo x) { x = new Foo(); } + private Foo FooProp { get { return _fooField; } } + + private static readonly IFoo _ifooField = (IFoo)new Foo(); + private IFoo IFooMethod() => _ifooField; + private void IFooRefMethod(ref IFoo x) { } + private void IFooOutMethod(out IFoo x) { x = new Foo(); } + private IFoo IFooProp { get { return _ifooField; } } + + private IFoo? {|#0:l0|} = null; + private IFoo? l1 = new Foo(); + private IFoo? l2 = new Foo(); + private IFoo? l3 = new Foo(); + private IFoo? l4 = new Foo(); + private IFoo? l5 = new Foo(); + private IFoo? l6 = new Foo(); + private IFoo? l7 = new Foo(); + private IFoo? l8 = new Foo(); + private IFoo? l9 = new Foo(); + public IFoo? l10 = new Foo(); + internal IFoo? l11 = new Foo(); + + public void Method(int x, Foo fooParam, IFoo ifooParam) + { + Foo fooLocal = new Foo(); + Foo[] fooArray = new Foo[0]; + Func fooDelegate = FooMethod; + + IFoo ifooLocal = new Foo(); + IFoo[] ifooArray = new IFoo[0]; + Func ifooDelegate = IFooMethod; + + switch (x) + { + case 0: l0 = new Foo(); break; + case 1: l0 = null; break; + case 2: l0 = null!; break; + case 3: l0 = _fooField; break; + case 4: l0 = fooArray[0]; break; + case 5: l0 = FooProp; break; + case 6: l0 = fooLocal; break; + case 7: l0 = fooParam; break; + case 8: l0 = FooMethod(); break; + case 9: l0 = fooDelegate(); break; + } + + l1 = ifooLocal; + l2 = _ifooField; + l3 = ifooArray[0]; + l4 = IFooProp; + l5 = IFooMethod(); + l6 = ifooParam; + l7 = ifooDelegate(); + IFooRefMethod(ref l8!); + IFooOutMethod(out l9); + + // induce virtual calls to trigger the diags + l0?.Bar(); + l1?.Bar(); + l2?.Bar(); + l3?.Bar(); + l4?.Bar(); + l5?.Bar(); + l6?.Bar(); + l7?.Bar(); + l8?.Bar(); + l9?.Bar(); + l10?.Bar(); + l11?.Bar(); + } + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForField) + .WithLocation(0) + .WithArguments("l0", "Example.IFoo?", "Example.Foo?")); + } + + [Fact] + public static async Task Methods() + { + const string Source = @" + using System; + using System.Threading.Tasks; + + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public interface I4 + { + IFoo M4(); + } + + public class Test : I4 + { + public IFoo M1() => new Foo(); + internal IFoo M2() => new Foo(); + private IFoo {|#0:M3|}() => new Foo(); + IFoo I4.M4() => new Foo(); + private static IFoo M5() => new Foo(); + private IFoo M6() => new Foo(); + + private async Task M7(Task stuff) + { + await stuff; + return ""Hello""; + } + + private async Task M8(Task stuff) + { + await stuff; + return (IFoo)new Foo(); + } + + private static Func _func = M5; + + private void Trigger() => Dispatch(M6); + private void Dispatch(Func func) => func(); + } + }"; + + await TestCSAsync(Source, + VerifyCS.Diagnostic(UseConcreteTypeAnalyzer.UseConcreteTypeForMethodReturn) + .WithLocation(0) + .WithArguments("M3", "Example.IFoo", "Example.Foo")); + } + + [Fact] + public static async Task OutParams() + { + const string Source = @" + namespace Example + { + public interface IFoo + { + void Bar(); + } + + public class Foo : IFoo + { + public void Bar() {} + } + + public class Test + { + public void M1() + { + if (!GetIFoo(out var f)) + { + f = new Foo(); + } + + f.Bar(); + } + + public bool GetIFoo(out IFoo ifoo) + { + ifoo = new Foo(); + return true; + } + } + }"; + + await TestCSAsync(Source); + } + + private static async Task TestCSAsync(string source, params DiagnosticResult[] diagnosticResults) + { + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net70, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.Preview, + }; + test.ExpectedDiagnostics.AddRange(diagnosticResults); + await test.RunAsync(); + } + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 44ee358950..ed679ecdfe 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1858 +Performance: HA, CA1800-CA1859 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260 Naming: CA1700-CA1727