|
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.UseConditionalExpression
{
internal static class UseConditionalExpressionForReturnHelpers
{
public static bool TryMatchPattern(
ISyntaxFacts syntaxFacts,
IConditionalOperation ifOperation,
ISymbol containingSymbol,
out bool isRef,
[NotNullWhen(true)] out IOperation? trueStatement,
[NotNullWhen(true)] out IOperation? falseStatement,
out IReturnOperation? trueReturn,
out IReturnOperation? falseReturn)
{
isRef = false;
trueReturn = null;
falseReturn = null;
trueStatement = ifOperation.WhenTrue;
falseStatement = ifOperation.WhenFalse;
// we support:
//
// if (expr)
// return a;
// else
// return b;
//
// and
//
// if (expr)
// return a;
//
// return b;
//
// note: either (but not both) of these statements can be throw-statements.
if (falseStatement == null)
{
if (ifOperation.Parent is not IBlockOperation parentBlock)
return false;
var ifIndex = parentBlock.Operations.IndexOf(ifOperation);
if (ifIndex < 0)
return false;
if (ifIndex + 1 >= parentBlock.Operations.Length)
return false;
falseStatement = parentBlock.Operations[ifIndex + 1];
if (falseStatement.IsImplicit)
return false;
}
trueStatement = UseConditionalExpressionHelpers.UnwrapSingleStatementBlock(trueStatement);
falseStatement = UseConditionalExpressionHelpers.UnwrapSingleStatementBlock(falseStatement);
// Both return-statements must be of the form "return value"
if (!IsReturnExprOrThrow(trueStatement) ||
!IsReturnExprOrThrow(falseStatement))
{
return false;
}
trueReturn = trueStatement as IReturnOperation;
falseReturn = falseStatement as IReturnOperation;
var trueThrow = trueStatement as IThrowOperation;
var falseThrow = falseStatement as IThrowOperation;
var anyReturn = trueReturn ?? falseReturn;
if (UseConditionalExpressionHelpers.HasInconvertibleThrowStatement(
syntaxFacts, anyReturn.GetRefKind(containingSymbol) != RefKind.None,
trueThrow, falseThrow))
{
return false;
}
if (trueReturn != null &&
falseReturn != null &&
trueReturn.Kind != falseReturn.Kind)
{
// Not allowed if these are different types of returns. i.e.
// "yield return ..." and "return ...".
return false;
}
if (trueReturn?.Kind == OperationKind.YieldBreak)
{
// This check is just paranoia. We likely shouldn't get here since we already
// checked if .ReturnedValue was null above.
return false;
}
if (trueReturn?.Kind == OperationKind.YieldReturn &&
ifOperation.WhenFalse == null)
{
// we have the following:
//
// if (...) {
// yield return ...
// }
//
// yield return ...
//
// It is *not* correct to replace this with:
//
// yield return ... ? ... ? ...
//
// as both yields need to be hit.
return false;
}
isRef = anyReturn.GetRefKind(containingSymbol) != RefKind.None;
return UseConditionalExpressionHelpers.CanConvert(
syntaxFacts, ifOperation, trueStatement, falseStatement);
}
private static bool IsReturnExprOrThrow(IOperation? statement)
{
// We can only convert a `throw expr` to a throw expression, not `throw;`
if (statement is IThrowOperation throwOperation)
return throwOperation.Exception != null;
return statement is IReturnOperation returnOp && returnOp.ReturnedValue != null;
}
}
}
|