|
// 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.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UsePatternCombinators;
using static AnalyzedPattern;
using static SyntaxFactory;
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UsePatternCombinators), Shared]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class CSharpUsePatternCombinatorsCodeFixProvider() : SyntaxEditorBasedCodeFixProvider
{
private const string SafeEquivalenceKey = nameof(CSharpUsePatternCombinatorsCodeFixProvider) + "_safe";
private const string UnsafeEquivalenceKey = nameof(CSharpUsePatternCombinatorsCodeFixProvider) + "_unsafe";
private static SyntaxKind MapToSyntaxKind(BinaryOperatorKind kind)
{
return kind switch
{
BinaryOperatorKind.LessThan => SyntaxKind.LessThanToken,
BinaryOperatorKind.GreaterThan => SyntaxKind.GreaterThanToken,
BinaryOperatorKind.LessThanOrEqual => SyntaxKind.LessThanEqualsToken,
BinaryOperatorKind.GreaterThanOrEqual => SyntaxKind.GreaterThanEqualsToken,
_ => throw ExceptionUtilities.UnexpectedValue(kind)
};
}
public override ImmutableArray<string> FixableDiagnosticIds
=> [IDEDiagnosticIds.UsePatternCombinatorsDiagnosticId];
protected override bool IncludeDiagnosticDuringFixAll(
Diagnostic diagnostic, Document document, string? equivalenceKey, CancellationToken cancellationToken)
{
var isSafe = CSharpUsePatternCombinatorsDiagnosticAnalyzer.IsSafe(diagnostic);
return isSafe == (equivalenceKey == SafeEquivalenceKey);
}
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.First();
var isSafe = CSharpUsePatternCombinatorsDiagnosticAnalyzer.IsSafe(diagnostic);
RegisterCodeFix(
context,
isSafe ? CSharpAnalyzersResources.Use_pattern_matching : CSharpAnalyzersResources.Use_pattern_matching_may_change_code_meaning,
isSafe ? SafeEquivalenceKey : UnsafeEquivalenceKey,
CodeActionPriority.Low);
return Task.CompletedTask;
}
protected override async Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
foreach (var diagnostic in diagnostics)
{
var location = diagnostic.Location;
var expression = editor.OriginalRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);
var operation = semanticModel.GetOperation(expression, cancellationToken);
RoslynDebug.AssertNotNull(operation);
var pattern = CSharpUsePatternCombinatorsAnalyzer.Analyze(operation);
RoslynDebug.AssertNotNull(pattern);
var patternSyntax = AsPatternSyntax(pattern).WithAdditionalAnnotations(Formatter.Annotation);
editor.ReplaceNode(expression, IsPatternExpression((ExpressionSyntax)pattern.Target.Syntax, patternSyntax));
}
}
private static PatternSyntax AsPatternSyntax(AnalyzedPattern pattern)
{
return pattern switch
{
Binary p => BinaryPattern(
p.IsDisjunctive ? SyntaxKind.OrPattern : SyntaxKind.AndPattern,
AsPatternSyntax(p.Left).Parenthesize(),
Token(p.Token.LeadingTrivia, p.IsDisjunctive ? SyntaxKind.OrKeyword : SyntaxKind.AndKeyword,
[.. p.Token.GetAllTrailingTrivia()]),
AsPatternSyntax(p.Right).Parenthesize()),
Constant p => ConstantPattern(AsExpressionSyntax(p.ExpressionSyntax, p)),
Source p => p.PatternSyntax,
Type p => TypePattern(p.TypeSyntax),
Relational p => RelationalPattern(Token(MapToSyntaxKind(p.OperatorKind)), AsExpressionSyntax(p.Value, p)),
Not p => UnaryPattern(AsPatternSyntax(p.Pattern).Parenthesize()),
var p => throw ExceptionUtilities.UnexpectedValue(p)
};
}
private static ExpressionSyntax AsExpressionSyntax(ExpressionSyntax expr, AnalyzedPattern p)
{
var semanticModel = p.Target.SemanticModel;
RoslynDebug.Assert(semanticModel != null);
var type = semanticModel.GetTypeInfo(expr).Type;
if (type != null)
{
// default literals are not permitted in patterns
if (expr.IsKind(SyntaxKind.DefaultLiteralExpression))
return DefaultExpression(type.GenerateTypeSyntax());
// 'null' is already the right form in a pattern, it does not need to be casted to anything else.
if (expr.IsKind(SyntaxKind.NullLiteralExpression))
return expr;
// if we have a nullable value type, only cast to the underlying type.
//
// `x is (long?)0` is not legal, only `x is (long)0` is.
var governingType = semanticModel.GetTypeInfo(p.Target.Syntax).Type.RemoveNullableIfPresent();
if (governingType != null && !governingType.Equals(type))
return CastExpression(governingType.GenerateTypeSyntax(), expr.Parenthesize()).WithAdditionalAnnotations(Simplifier.Annotation);
}
return expr.Parenthesize();
}
}
|