in src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs [447:621]
public static bool CanReplaceWithLValue(
this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (expression.IsKind(SyntaxKind.StackAllocArrayCreationExpression))
{
// Stack alloc is very interesting. While it appears to be an expression, it is only
// such so it can appear in a variable decl. It is not a normal expression that can
// go anywhere.
return false;
}
if (expression.IsKind(SyntaxKind.BaseExpression) ||
expression.IsKind(SyntaxKind.CollectionInitializerExpression) ||
expression.IsKind(SyntaxKind.ObjectInitializerExpression) ||
expression.IsKind(SyntaxKind.ComplexElementInitializerExpression))
{
return false;
}
// literal can be always replaced.
if (expression is LiteralExpressionSyntax && !expression.IsParentKind(SyntaxKind.UnaryMinusExpression))
{
return true;
}
if (expression is TupleExpressionSyntax)
{
return true;
}
if (!(expression is ObjectCreationExpressionSyntax) &&
!(expression is AnonymousObjectCreationExpressionSyntax) &&
!expression.IsLeftSideOfAssignExpression())
{
var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
if (!symbolInfo.GetBestOrAllSymbols().All(CanReplace))
{
// If the expression is actually a reference to a type, then it can't be replaced
// with an arbitrary expression.
return false;
}
}
// If we are a conditional access expression:
// case (1) : obj?.Method(), obj1.obj2?.Property
// case (2) : obj?.GetAnotherObj()?.Length, obj?.AnotherObj?.Length
// in case (1), the entire expression forms the conditional access expression, which can be replaced with an LValue.
// in case (2), the nested conditional access expression is ".GetAnotherObj()?.Length" or ".AnotherObj()?.Length"
// essentially, the first expression (before the operator) in a nested conditional access expression
// is some form of member binding expression and they cannot be replaced with an LValue.
if (expression.IsKind(SyntaxKind.ConditionalAccessExpression))
{
return expression.Parent.Kind() != SyntaxKind.ConditionalAccessExpression;
}
switch (expression.Parent.Kind())
{
case SyntaxKind.InvocationExpression:
// Technically, you could introduce an LValue for "Goo" in "Goo()" even if "Goo" binds
// to a method. (i.e. by assigning to a Func<...> type). However, this is so contrived
// and none of the features that use this extension consider this replaceable.
if (expression.IsKind(SyntaxKind.IdentifierName) || expression is MemberAccessExpressionSyntax)
{
// If it looks like a method then we don't allow it to be replaced if it is a
// method (or if it doesn't bind).
var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
return symbolInfo.GetBestOrAllSymbols().Any() && !symbolInfo.GetBestOrAllSymbols().Any(s => s is IMethodSymbol);
}
else
{
// It doesn't look like a method, we allow this to be replaced.
return true;
}
// If the parent is a conditional access expression, we could introduce an LValue
// for the given expression, unless it is itself a MemberBindingExpression or starts with one.
// Case (1) : The WhenNotNull clause always starts with a MemberBindingExpression.
// expression '.Method()' in a?.Method()
// Case (2) : The Expression clause always starts with a MemberBindingExpression if
// the grandparent is a conditional access expression.
// expression '.Method' in a?.Method()?.Length
// Case (3) : The child Conditional access expression always starts with a MemberBindingExpression if
// the parent is a conditional access expression. This case is already covered before the parent kind switch
case SyntaxKind.ConditionalAccessExpression:
var parentConditionalAccessExpression = (ConditionalAccessExpressionSyntax)expression.Parent;
return expression != parentConditionalAccessExpression.WhenNotNull &&
!parentConditionalAccessExpression.Parent.IsKind(SyntaxKind.ConditionalAccessExpression);
case SyntaxKind.IsExpression:
case SyntaxKind.AsExpression:
// Can't introduce a variable for the type portion of an is/as check.
var isOrAsExpression = (BinaryExpressionSyntax)expression.Parent;
return expression == isOrAsExpression.Left;
case SyntaxKind.EqualsValueClause:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.ArrayInitializerExpression:
case SyntaxKind.CollectionInitializerExpression:
case SyntaxKind.Argument:
case SyntaxKind.AttributeArgument:
case SyntaxKind.AnonymousObjectMemberDeclarator:
case SyntaxKind.ArrowExpressionClause:
case SyntaxKind.AwaitExpression:
case SyntaxKind.ReturnStatement:
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.ArrayRankSpecifier:
case SyntaxKind.ConditionalExpression:
case SyntaxKind.IfStatement:
case SyntaxKind.CatchFilterClause:
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.ThrowStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.InterpolatedStringExpression:
case SyntaxKind.ComplexElementInitializerExpression:
case SyntaxKind.Interpolation:
case SyntaxKind.RefExpression:
// Direct parent kind checks.
return true;
}
if (expression.Parent is PrefixUnaryExpressionSyntax)
{
if (!(expression is LiteralExpressionSyntax && expression.IsParentKind(SyntaxKind.UnaryMinusExpression)))
{
return true;
}
}
var parentNonExpression = expression.GetAncestors().SkipWhile(n => n is ExpressionSyntax).FirstOrDefault();
var topExpression = expression;
while (topExpression.Parent is TypeSyntax)
{
topExpression = (TypeSyntax)topExpression.Parent;
}
if (parentNonExpression != null &&
parentNonExpression.IsKind(SyntaxKind.FromClause) &&
topExpression != null &&
((FromClauseSyntax)parentNonExpression).Type == topExpression)
{
return false;
}
// Parent type checks.
if (expression.Parent is PostfixUnaryExpressionSyntax ||
expression.Parent is BinaryExpressionSyntax ||
expression.Parent is AssignmentExpressionSyntax ||
expression.Parent is QueryClauseSyntax ||
expression.Parent is SelectOrGroupClauseSyntax ||
expression.Parent is CheckedExpressionSyntax)
{
return true;
}
// Specific child checks.
if (expression.CheckParent<CommonForEachStatementSyntax>(f => f.Expression == expression) ||
expression.CheckParent<MemberAccessExpressionSyntax>(m => m.Expression == expression) ||
expression.CheckParent<CastExpressionSyntax>(c => c.Expression == expression))
{
return true;
}
// Misc checks.
if ((expression.IsParentKind(SyntaxKind.NameEquals) && expression.Parent.IsParentKind(SyntaxKind.AttributeArgument)) ||
expression.IsLeftSideOfAnyAssignExpression())
{
return true;
}
return false;
}