diff --git a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs index 2ba5d60c76a3e..d65a510d172c2 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnusedParametersAndValues/RemoveUnusedParametersTests.cs @@ -1503,6 +1503,36 @@ public C(int [|i|]) }"); } + [WorkItem(56317, "https://github.com/dotnet/roslyn/issues/56317")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedParameters)] + public async Task NotImplementedException_NoDiagnostic4() + { + await TestDiagnosticMissingAsync( +@"using System; + +class C +{ + private int Goo(int [|i|]) + => throw new NotImplementedException(); +}"); + } + + [WorkItem(56317, "https://github.com/dotnet/roslyn/issues/56317")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedParameters)] + public async Task NotImplementedException_NoDiagnostic5() + { + await TestDiagnosticMissingAsync( +@"using System; + +class C +{ + private int Goo(int [|i|]) + { + throw new NotImplementedException(); + } +}"); + } + [WorkItem(41236, "https://github.com/dotnet/roslyn/issues/41236")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedParameters)] public async Task NotImplementedException_MultipleStatements1() diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs index f110f9e90fdb9..54e1f0deb591e 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.SymbolStartAnalyzer.BlockAnalyzer.cs @@ -152,9 +152,18 @@ static bool IsSingleThrowNotImplementedOperation(IOperation firstBlock) if (firstOp == null) return false; - // unwrap: { throw new NYI(); } if (firstOp is IExpressionStatementOperation expressionStatement) + { + // unwrap: { throw new NYI(); } firstOp = expressionStatement.Operation; + } + else if (firstOp is IReturnOperation returnOperation) + { + // unwrap: 'int M(int p) => throw new NYI();' + // For this case, the throw operation is wrapped within a conversion operation to 'int', + // which in turn is wrapped within a return operation. + firstOp = returnOperation.ReturnedValue.WalkDownConversion(); + } // => throw new NotImplementedOperation(...) return IsThrowNotImplementedOperation(notImplementedExceptionType, firstOp); @@ -177,7 +186,7 @@ static bool IsSingleThrowNotImplementedOperation(IOperation firstBlock) return firstOp; } - static bool IsThrowNotImplementedOperation(INamedTypeSymbol notImplementedExceptionType, IOperation operation) + static bool IsThrowNotImplementedOperation(INamedTypeSymbol notImplementedExceptionType, IOperation? operation) => operation is IThrowOperation throwOperation && UnwrapImplicitConversion(throwOperation.Exception) is IObjectCreationOperation objectCreation && notImplementedExceptionType.Equals(objectCreation.Type); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/OperationExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/OperationExtensions.cs index 3bde34e3df8b5..b9027c0239b53 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/OperationExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/OperationExtensions.cs @@ -370,5 +370,20 @@ public static bool IsNumericLiteral(this IOperation operation) public static bool IsNullLiteral(this IOperation operand) => operand is ILiteralOperation { ConstantValue: { HasValue: true, Value: null } }; + + /// + /// Walks down consecutive conversion operations until an operand is reached that isn't a conversion operation. + /// + /// The starting operation. + /// The inner non conversion operation or the starting operation if it wasn't a conversion operation. + public static IOperation? WalkDownConversion(this IOperation? operation) + { + while (operation is IConversionOperation conversionOperation) + { + operation = conversionOperation.Operand; + } + + return operation; + } } }