| File: src\roslyn\src\Analyzers\CSharp\Analyzers\HiddenExplicitCast\CSharpHiddenExplicitCastDiagnosticAnalyzer.cs | Web Access |
| Project: src\roslyn\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.CSharp.Analyzers.HiddenExplicitCast; [DiagnosticAnalyzer(LanguageNames.CSharp)] internal sealed class CSharpHiddenExplicitCastDiagnosticAnalyzer() : AbstractBuiltInCodeStyleDiagnosticAnalyzer( diagnosticId: IDEDiagnosticIds.HiddenExplicitCastDiagnosticId, EnforceOnBuildValues.HiddenExplicitCast, CodeStyleOptions2.PreferNonHiddenExplicitCastInSource, title: new LocalizableResourceString(nameof(AnalyzersResources.Add_explicit_cast), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)), messageFormat: new LocalizableResourceString(nameof(AnalyzersResources._0_implicitly_converts_1_to_2_Add_an_explicit_cast_to_make_intent_clearer_as_it_may_fail_at_runtime), AnalyzersResources.ResourceManager, typeof(AnalyzersResources))) { public const string Type = nameof(Type); public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; protected override void InitializeWorker(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.CastExpression); private static IConversionOperation? GetInitialOperation( SemanticModel semanticModel, CastExpressionSyntax castExpression, CancellationToken cancellationToken) { // Due to issues getting IConversionOperations from the semantic model directly from a CastExpressionSyntax (see // https://github.com/dotnet/roslyn/issues/81781). To work around this, we instead get the IOp for the casted // inner expression instead, and then walk up the IOperation tree to find the highest explicit conversion // corresponding to this cast expression node. // IOp tree does not return operations for parentheses or suppressions, so walk down those first. var currentExpression = castExpression.Expression; while (true) { var inner = currentExpression.WalkDownParentheses().WalkDownSuppressions(); if (inner == currentExpression) break; currentExpression = inner; } var innerOperation = semanticModel.GetOperation(currentExpression, cancellationToken); if (innerOperation is null) return null; // Keep walking upwards through conversions. We want the highest one that is explicit and corresponds to this // cast expression. IConversionOperation? highestExplicitConversion = null; for (var current = innerOperation.Parent; current is IConversionOperation conversionOperation; current = current.Parent) { if (conversionOperation.GetConversion().IsExplicit && conversionOperation.Syntax == castExpression) highestExplicitConversion = conversionOperation; } return highestExplicitConversion; } private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var semanticModel = context.SemanticModel; var cancellationToken = context.CancellationToken; var castExpression = (CastExpressionSyntax)context.Node; var option = context.GetAnalyzerOptions().PreferNonHiddenExplicitCastInSource; if (!option.Value || ShouldSkipAnalysis(context, option.Notification)) { return; } var outerConversionOperation = GetInitialOperation(semanticModel, castExpression, cancellationToken); if (outerConversionOperation is null) return; // This cast needs to be an explicit conversion. We're looking for the case where this explicit conversion in // source causes an inner explicit conversion to be emitted in a hidden fashion by the compiler. var outerConversion = outerConversionOperation.GetConversion(); if (!outerConversion.IsExplicit) return; // Has to be multiple conversions. if (outerConversionOperation.Operand is not IConversionOperation innerConversionOperation) return; // The inner one has to be generated by the compiler, not the user. if (!innerConversionOperation.IsImplicit) return; // The inner one has to be explicit as well. This is the hidden explicit cast we're looking for. var innerConversion = innerConversionOperation.GetConversion(); if (!innerConversion.IsExplicit) return; if (outerConversionOperation.Type is null || innerConversionOperation.Type is null || innerConversionOperation.Operand.Type is null) return; var typeToInsertCastFor = innerConversionOperation.Type.ToMinimalDisplayString(semanticModel, castExpression.SpanStart); context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, castExpression.GetLocation(), option.Notification, context.Options, additionalLocations: null, properties: ImmutableDictionary<string, string?>.Empty.Add(Type, typeToInsertCastFor), $"({outerConversionOperation.Type.ToMinimalDisplayString(semanticModel, castExpression.SpanStart)})", innerConversionOperation.Operand.Type.ToMinimalDisplayString(semanticModel, castExpression.SpanStart), typeToInsertCastFor)); } }