// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle.TypeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
internal static partial class ExpressionSyntaxExtensions
{
public static ExpressionSyntax WalkUpParentheses(this ExpressionSyntax expression)
{
while (expression.IsParentKind(SyntaxKind.ParenthesizedExpression))
{
expression = (ExpressionSyntax)expression.Parent;
}
return expression;
}
public static ExpressionSyntax WalkDownParentheses(this ExpressionSyntax expression)
{
while (expression.IsKind(SyntaxKind.ParenthesizedExpression))
{
expression = ((ParenthesizedExpressionSyntax)expression).Expression;
}
return expression;
}
public static ExpressionSyntax Parenthesize(
this ExpressionSyntax expression, bool includeElasticTrivia = true, bool addSimplifierAnnotation = true)
{
// a 'ref' expression should never be parenthesized. It fundamentally breaks the code.
// This is because, from the language's perspective there is no such thing as a ref
// expression. instead, there are constructs like ```return ref expr``` or
// ```x ? ref expr1 : ref expr2```, or ```ref int a = ref expr``` in these cases, the
// ref's do not belong to the exprs, but instead belong to the parent construct. i.e.
// ```return ref``` or ``` ? ref ... : ref ... ``` or ``` ... = ref ...```. For
// parsing convenience, and to prevent having to update all these constructs, we settled
// on a ref-expression node. But this node isn't a true expression that be operated
// on like with everything else.
if (expression.IsKind(SyntaxKind.RefExpression))
{
return expression;
}
var result = ParenthesizeWorker(expression, includeElasticTrivia);
return addSimplifierAnnotation
? result.WithAdditionalAnnotations(Simplifier.Annotation)
: result;
}
private static ExpressionSyntax ParenthesizeWorker(
this ExpressionSyntax expression, bool includeElasticTrivia)
{
var withoutTrivia = expression.WithoutTrivia();
var parenthesized = includeElasticTrivia
? SyntaxFactory.ParenthesizedExpression(withoutTrivia)
: SyntaxFactory.ParenthesizedExpression(
SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.OpenParenToken, SyntaxTriviaList.Empty),
withoutTrivia,
SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.CloseParenToken, SyntaxTriviaList.Empty));
return parenthesized.WithTriviaFrom(expression);
}
public static CastExpressionSyntax Cast(
this ExpressionSyntax expression,
ITypeSymbol targetType)
{
return SyntaxFactory.CastExpression(
type: targetType.GenerateTypeSyntax(),
expression: expression.Parenthesize())
.WithAdditionalAnnotations(Simplifier.Annotation);
}
///
/// Adds to if it does not contain an anonymous
/// type and binds to the same type at the given .
///
public static ExpressionSyntax CastIfPossible(
this ExpressionSyntax expression,
ITypeSymbol targetType,
int position,
SemanticModel semanticModel)
{
if (targetType.ContainsAnonymousType())
{
return expression;
}
if (targetType.Kind == SymbolKind.DynamicType)
{
targetType = semanticModel.Compilation.GetSpecialType(SpecialType.System_Object);
}
var typeSyntax = targetType.GenerateTypeSyntax();
var type = semanticModel.GetSpeculativeTypeInfo(
position,
typeSyntax,
SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
if (!targetType.Equals(type))
{
return expression;
}
var castExpression = expression.Cast(targetType);
// Ensure that inserting the cast doesn't change the semantics.
var specAnalyzer = new SpeculationAnalyzer(expression, castExpression, semanticModel, CancellationToken.None);
var speculativeSemanticModel = specAnalyzer.SpeculativeSemanticModel;
if (speculativeSemanticModel == null)
{
return expression;
}
var speculatedCastExpression = (CastExpressionSyntax)specAnalyzer.ReplacedExpression;
if (!speculatedCastExpression.IsUnnecessaryCast(speculativeSemanticModel, CancellationToken.None))
{
return expression;
}
return castExpression;
}
public static bool IsQualifiedCrefName(this ExpressionSyntax expression)
{
return expression.IsParentKind(SyntaxKind.NameMemberCref) && expression.Parent.IsParentKind(SyntaxKind.QualifiedCref);
}
public static bool IsMemberAccessExpressionName(this ExpressionSyntax expression)
{
return (expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) && ((MemberAccessExpressionSyntax)expression.Parent).Name == expression) ||
(IsMemberBindingExpressionName(expression));
}
public static bool IsAnyMemberAccessExpressionName(this ExpressionSyntax expression)
{
if (expression == null)
{
return false;
}
return expression == (expression.Parent as MemberAccessExpressionSyntax)?.Name ||
expression.IsMemberBindingExpressionName();
}
private static bool IsMemberBindingExpressionName(this ExpressionSyntax expression)
{
return expression.IsParentKind(SyntaxKind.MemberBindingExpression) &&
((MemberBindingExpressionSyntax)expression.Parent).Name == expression;
}
public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression)
{
return expression.IsParentKind(SyntaxKind.QualifiedName) && ((QualifiedNameSyntax)expression.Parent).Right == expression;
}
public static bool IsRightSideOfColonColon(this ExpressionSyntax expression)
{
return expression.IsParentKind(SyntaxKind.AliasQualifiedName) && ((AliasQualifiedNameSyntax)expression.Parent).Name == expression;
}
public static bool IsRightSideOfDot(this ExpressionSyntax name)
{
return IsMemberAccessExpressionName(name) || IsRightSideOfQualifiedName(name) || IsQualifiedCrefName(name);
}
public static bool IsRightSideOfDotOrArrow(this ExpressionSyntax name)
{
return IsAnyMemberAccessExpressionName(name) || IsRightSideOfQualifiedName(name);
}
public static bool IsRightSideOfDotOrColonColon(this ExpressionSyntax name)
{
return IsRightSideOfDot(name) || IsRightSideOfColonColon(name);
}
public static bool IsRightSideOfDotOrArrowOrColonColon(this ExpressionSyntax name)
{
return IsRightSideOfDotOrArrow(name) || IsRightSideOfColonColon(name);
}
public static bool IsRightOfCloseParen(this ExpressionSyntax expression)
{
var firstToken = expression.GetFirstToken();
return firstToken.Kind() != SyntaxKind.None
&& firstToken.GetPreviousToken().Kind() == SyntaxKind.CloseParenToken;
}
public static bool IsLeftSideOfDot(this ExpressionSyntax expression)
{
if (expression == null)
{
return false;
}
return IsLeftSideOfQualifiedName(expression) ||
IsLeftSideOfSimpleMemberAccessExpression(expression);
}
public static bool IsLeftSideOfSimpleMemberAccessExpression(this ExpressionSyntax expression)
=> (expression?.Parent).IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess) &&
memberAccess.Expression == expression;
public static bool IsLeftSideOfDotOrArrow(this ExpressionSyntax expression)
=> IsLeftSideOfQualifiedName(expression) ||
(expression.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression == expression);
public static bool IsLeftSideOfQualifiedName(this ExpressionSyntax expression)
=> (expression?.Parent).IsKind(SyntaxKind.QualifiedName, out QualifiedNameSyntax qualifiedName) && qualifiedName.Left == expression;
public static bool IsLeftSideOfExplicitInterfaceSpecifier(this NameSyntax name)
=> name.IsParentKind(SyntaxKind.ExplicitInterfaceSpecifier);
public static bool IsExpressionOfInvocation(this ExpressionSyntax expression)
=> (expression?.Parent).IsKind(SyntaxKind.InvocationExpression, out InvocationExpressionSyntax invocation) &&
invocation.Expression == expression;
public static bool TryGetNameParts(this ExpressionSyntax expression, out IList parts)
{
var partsList = new List();
if (!TryGetNameParts(expression, partsList))
{
parts = null;
return false;
}
parts = partsList;
return true;
}
public static bool TryGetNameParts(this ExpressionSyntax expression, List parts)
{
if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
var memberAccess = (MemberAccessExpressionSyntax)expression;
if (!TryGetNameParts(memberAccess.Expression, parts))
{
return false;
}
return AddSimpleName(memberAccess.Name, parts);
}
else if (expression.IsKind(SyntaxKind.QualifiedName))
{
var qualifiedName = (QualifiedNameSyntax)expression;
if (!TryGetNameParts(qualifiedName.Left, parts))
{
return false;
}
return AddSimpleName(qualifiedName.Right, parts);
}
else if (expression is SimpleNameSyntax simpleName)
{
return AddSimpleName(simpleName, parts);
}
else
{
return false;
}
}
private static bool AddSimpleName(SimpleNameSyntax simpleName, List parts)
{
if (!simpleName.IsKind(SyntaxKind.IdentifierName))
{
return false;
}
parts.Add(simpleName.Identifier.ValueText);
return true;
}
public static bool IsAnyLiteralExpression(this ExpressionSyntax expression)
{
return
expression.IsKind(SyntaxKind.CharacterLiteralExpression) ||
expression.IsKind(SyntaxKind.FalseLiteralExpression) ||
expression.IsKind(SyntaxKind.NullLiteralExpression) ||
expression.IsKind(SyntaxKind.NumericLiteralExpression) ||
expression.IsKind(SyntaxKind.StringLiteralExpression) ||
expression.IsKind(SyntaxKind.TrueLiteralExpression);
}
public static bool IsInConstantContext(this ExpressionSyntax expression)
{
if (expression.GetAncestor() != null)
{
return true;
}
var attributeArgument = expression.GetAncestor();
if (attributeArgument != null)
{
if (attributeArgument.NameEquals == null ||
expression != attributeArgument.NameEquals.Name)
{
return true;
}
}
if (expression.IsParentKind(SyntaxKind.ConstantPattern))
{
return true;
}
// note: the above list is not intended to be exhaustive. If more cases
// are discovered that should be considered 'constant' contexts in the
// language, then this should be updated accordingly.
return false;
}
public static bool IsInOutContext(this ExpressionSyntax expression)
{
return
expression?.Parent is ArgumentSyntax argument &&
argument.Expression == expression &&
argument.RefOrOutKeyword.Kind() == SyntaxKind.OutKeyword;
}
public static bool IsInRefContext(this ExpressionSyntax expression)
=> expression.IsParentKind(SyntaxKind.RefExpression) ||
(expression?.Parent as ArgumentSyntax)?.RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword;
public static bool IsInInContext(this ExpressionSyntax expression)
=> (expression?.Parent as ArgumentSyntax)?.RefKindKeyword.Kind() == SyntaxKind.InKeyword;
private static ExpressionSyntax GetExpressionToAnalyzeForWrites(ExpressionSyntax expression)
{
if (expression.IsRightSideOfDotOrArrow())
{
expression = expression.Parent as ExpressionSyntax;
}
expression = expression.WalkUpParentheses();
return expression;
}
public static bool IsOnlyWrittenTo(this ExpressionSyntax expression)
{
expression = GetExpressionToAnalyzeForWrites(expression);
if (expression != null)
{
if (expression.IsInOutContext())
{
return true;
}
if (expression.Parent != null)
{
if (expression.IsLeftSideOfAssignExpression())
{
return true;
}
if (expression.IsAttributeNamedArgumentIdentifier())
{
return true;
}
}
if (IsExpressionOfArgumentInDeconstruction(expression))
{
return true;
}
}
return false;
}
///
/// If this declaration or identifier is part of a deconstruction, find the deconstruction.
/// If found, returns either an assignment expression or a foreach variable statement.
/// Returns null otherwise.
///
/// copied from SyntaxExtensions.GetContainingDeconstruction
///
private static bool IsExpressionOfArgumentInDeconstruction(ExpressionSyntax expr)
{
if (!expr.IsParentKind(SyntaxKind.Argument))
{
return false;
}
while (true)
{
var parent = expr.Parent;
if (parent == null)
{
return false;
}
switch (parent.Kind())
{
case SyntaxKind.Argument:
if (parent.Parent?.Kind() == SyntaxKind.TupleExpression)
{
expr = (TupleExpressionSyntax)parent.Parent;
continue;
}
return false;
case SyntaxKind.SimpleAssignmentExpression:
if (((AssignmentExpressionSyntax)parent).Left == expr)
{
return true;
}
return false;
case SyntaxKind.ForEachVariableStatement:
if (((ForEachVariableStatementSyntax)parent).Variable == expr)
{
return true;
}
return false;
default:
return false;
}
}
}
public static bool IsWrittenTo(this ExpressionSyntax expression)
{
expression = GetExpressionToAnalyzeForWrites(expression);
if (expression.IsOnlyWrittenTo())
{
return true;
}
if (expression.IsInRefContext())
{
return true;
}
// We're written if we're used in a ++, or -- expression.
if (expression.IsOperandOfIncrementOrDecrementExpression())
{
return true;
}
if (expression.IsLeftSideOfAnyAssignExpression())
{
return true;
}
return false;
}
public static bool IsAttributeNamedArgumentIdentifier(this ExpressionSyntax expression)
{
var nameEquals = expression?.Parent as NameEqualsSyntax;
return nameEquals.IsParentKind(SyntaxKind.AttributeArgument);
}
public static bool IsOperandOfIncrementOrDecrementExpression(this ExpressionSyntax expression)
{
if (expression != null)
{
switch (expression.Parent.Kind())
{
case SyntaxKind.PostIncrementExpression:
case SyntaxKind.PreIncrementExpression:
case SyntaxKind.PostDecrementExpression:
case SyntaxKind.PreDecrementExpression:
return true;
}
}
return false;
}
public static bool IsNamedArgumentIdentifier(this ExpressionSyntax expression)
{
return expression is IdentifierNameSyntax && expression.Parent is NameColonSyntax;
}
public static bool IsInsideNameOfExpression(
this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken)
{
var invocation = expression?.GetAncestor();
if (invocation?.Expression is IdentifierNameSyntax name &&
name.Identifier.Text == SyntaxFacts.GetText(SyntaxKind.NameOfKeyword))
{
return semanticModel.GetMemberGroup(name, cancellationToken).IsDefaultOrEmpty;
}
return false;
}
private static bool CanReplace(ISymbol symbol)
{
switch (symbol.Kind)
{
case SymbolKind.Field:
case SymbolKind.Local:
case SymbolKind.Method:
case SymbolKind.Parameter:
case SymbolKind.Property:
case SymbolKind.RangeVariable:
return true;
}
return false;
}
public static bool CanReplaceWithRValue(
this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken)
{
// An RValue can't be written into.
// i.e. you can't replace "a" in "a = b" with "Goo() = b".
return
expression != null &&
!expression.IsWrittenTo() &&
CanReplaceWithLValue(expression, semanticModel, cancellationToken);
}
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:
case SyntaxKind.LockStatement:
case SyntaxKind.ElementAccessExpression:
// 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(f => f.Expression == expression) ||
expression.CheckParent(m => m.Expression == expression) ||
expression.CheckParent(c => c.Expression == expression))
{
return true;
}
// Misc checks.
if ((expression.IsParentKind(SyntaxKind.NameEquals) && expression.Parent.IsParentKind(SyntaxKind.AttributeArgument)) ||
expression.IsLeftSideOfAnyAssignExpression())
{
return true;
}
return false;
}
public static bool CanAccessInstanceAndStaticMembersOffOf(
this ExpressionSyntax expression,
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
// Check for the Color Color case.
//
// color color: if you bind "A" and you get a symbol and the type of that symbol is
// Q; and if you bind "A" *again* as a type and you get type Q, then both A.static
// and A.instance are permitted
if (expression is IdentifierNameSyntax)
{
var instanceSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).GetAnySymbol();
if (!(instanceSymbol is INamespaceOrTypeSymbol))
{
var instanceType = instanceSymbol.GetSymbolType();
if (instanceType != null)
{
var speculativeSymbolInfo = semanticModel.GetSpeculativeSymbolInfo(expression.SpanStart, expression, SpeculativeBindingOption.BindAsTypeOrNamespace);
if (speculativeSymbolInfo.CandidateReason != CandidateReason.NotATypeOrNamespace)
{
var staticType = speculativeSymbolInfo.GetAnySymbol().GetSymbolType();
return SymbolEquivalenceComparer.Instance.Equals(instanceType, staticType);
}
}
}
}
return false;
}
public static bool TryReduceOrSimplifyExplicitName(
this ExpressionSyntax expression,
SemanticModel semanticModel,
out ExpressionSyntax replacementNode,
out TextSpan issueSpan,
OptionSet optionSet,
CancellationToken cancellationToken)
{
if (TryReduceExplicitName(expression, semanticModel, out var replacementTypeNode, out issueSpan, optionSet, cancellationToken))
{
replacementNode = replacementTypeNode;
return true;
}
return TrySimplify(expression, semanticModel, out replacementNode, out issueSpan);
}
private static bool TryReduceExplicitName(
ExpressionSyntax expression,
SemanticModel semanticModel,
out TypeSyntax replacementNode,
out TextSpan issueSpan,
OptionSet optionSet,
CancellationToken cancellationToken)
{
replacementNode = null;
issueSpan = default;
if (expression.ContainsInterleavedDirective(cancellationToken))
return false;
if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess))
return TryReduceMemberAccessExpression(memberAccess, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken);
if (expression is NameSyntax name)
return TryReduceName(name, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken);
return false;
}
private static bool TryReduceMemberAccessExpression(
MemberAccessExpressionSyntax memberAccess,
SemanticModel semanticModel,
out TypeSyntax replacementNode,
out TextSpan issueSpan,
OptionSet optionSet,
CancellationToken cancellationToken)
{
replacementNode = null;
issueSpan = default;
if (memberAccess.Name == null || memberAccess.Expression == null)
return false;
// if this node is annotated as being a SpecialType, let's use this information.
if (memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind))
{
replacementNode = SyntaxFactory.PredefinedType(
SyntaxFactory.Token(
memberAccess.GetLeadingTrivia(),
GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())),
memberAccess.GetTrailingTrivia()));
issueSpan = memberAccess.Span;
return true;
}
if (memberAccess.Expression.IsKind(SyntaxKind.ThisExpression) &&
!SimplificationHelpers.ShouldSimplifyMemberAccessExpression(semanticModel, memberAccess.Name, optionSet))
{
return false;
}
// if this node is on the left side, we could simplify to aliases
if (!memberAccess.IsRightSideOfDot())
{
// Check if we need to replace this syntax with an alias identifier
if (TryReplaceExpressionWithAlias(memberAccess, semanticModel, cancellationToken, out var aliasReplacement))
{
// get the token text as it appears in source code to preserve e.g. unicode character escaping
var text = aliasReplacement.Name;
var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault();
if (syntaxRef != null)
{
var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier;
text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString();
}
replacementNode = SyntaxFactory.IdentifierName(
memberAccess.Name.Identifier.CopyAnnotationsTo(SyntaxFactory.Identifier(
memberAccess.GetLeadingTrivia(),
SyntaxKind.IdentifierToken,
text,
aliasReplacement.Name,
memberAccess.GetTrailingTrivia())));
replacementNode = memberAccess.CopyAnnotationsTo(replacementNode);
replacementNode = memberAccess.Name.CopyAnnotationsTo(replacementNode);
issueSpan = memberAccess.Span;
// In case the alias name is the same as the last name of the alias target, we only include
// the left part of the name in the unnecessary span to Not confuse uses.
if (memberAccess.Name.Identifier.ValueText == ((IdentifierNameSyntax)replacementNode).Identifier.ValueText)
{
issueSpan = memberAccess.Expression.Span;
}
return true;
}
// Check if the Expression can be replaced by Predefined Type keyword
if (PreferPredefinedTypeKeywordInMemberAccess(memberAccess, optionSet, semanticModel))
{
var symbol = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol;
if (symbol != null && symbol.IsKind(SymbolKind.NamedType))
{
var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)symbol).SpecialType);
if (keywordKind != SyntaxKind.None)
{
replacementNode = CreatePredefinedTypeSyntax(memberAccess, keywordKind);
replacementNode = replacementNode
.WithAdditionalAnnotations(new SyntaxAnnotation(
nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess)));
issueSpan = memberAccess.Span; // we want to show the whole expression as unnecessary
return true;
}
}
}
}
// Try to eliminate cases without actually calling CanReplaceWithReducedName. For expressions of the form
// 'this.Name' or 'base.Name', no additional check here is required.
if (!memberAccess.Expression.IsKind(SyntaxKind.ThisExpression, SyntaxKind.BaseExpression))
{
var actualSymbol = semanticModel.GetSymbolInfo(memberAccess.Name, cancellationToken);
GetReplacementCandidates(
semanticModel,
memberAccess,
actualSymbol,
out var speculativeSymbols,
out var speculativeNamespacesAndTypes);
if (!IsReplacementCandidate(actualSymbol, speculativeSymbols, speculativeNamespacesAndTypes))
{
return false;
}
}
replacementNode = memberAccess.GetNameWithTriviaMoved();
issueSpan = memberAccess.Expression.Span;
return CanReplaceWithReducedName(memberAccess, replacementNode, semanticModel, cancellationToken);
}
public static SimpleNameSyntax GetNameWithTriviaMoved(this MemberAccessExpressionSyntax memberAccess)
=> memberAccess.Name
.WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess))
.WithTrailingTrivia(memberAccess.GetTrailingTrivia());
private static void GetReplacementCandidates(
SemanticModel semanticModel,
MemberAccessExpressionSyntax memberAccess,
SymbolInfo actualSymbol,
out ImmutableArray speculativeSymbols,
out ImmutableArray speculativeNamespacesAndTypes)
{
bool containsNamespaceOrTypeSymbol;
bool containsOtherSymbol;
if (actualSymbol.Symbol is object)
{
containsNamespaceOrTypeSymbol = actualSymbol.Symbol is INamespaceOrTypeSymbol;
containsOtherSymbol = !containsNamespaceOrTypeSymbol;
}
else if (!actualSymbol.CandidateSymbols.IsDefaultOrEmpty)
{
containsNamespaceOrTypeSymbol = actualSymbol.CandidateSymbols.Any(symbol => symbol is INamespaceOrTypeSymbol);
containsOtherSymbol = actualSymbol.CandidateSymbols.Any(symbol => !(symbol is INamespaceOrTypeSymbol));
}
else
{
speculativeSymbols = ImmutableArray.Empty;
speculativeNamespacesAndTypes = ImmutableArray.Empty;
return;
}
speculativeSymbols = containsOtherSymbol
? semanticModel.LookupSymbols(memberAccess.SpanStart, name: memberAccess.Name.Identifier.ValueText)
: ImmutableArray.Empty;
speculativeNamespacesAndTypes = containsNamespaceOrTypeSymbol
? semanticModel.LookupNamespacesAndTypes(memberAccess.SpanStart, name: memberAccess.Name.Identifier.ValueText)
: ImmutableArray.Empty;
}
///
/// Determines if and
/// together contain a superset of the symbols in .
///
private static bool IsReplacementCandidate(SymbolInfo actualSymbol, ImmutableArray speculativeSymbols, ImmutableArray speculativeNamespacesAndTypes)
{
if (speculativeSymbols.IsEmpty && speculativeNamespacesAndTypes.IsEmpty)
{
return false;
}
if (actualSymbol.Symbol is object)
{
return speculativeSymbols.Contains(actualSymbol.Symbol, CandidateSymbolEqualityComparer.Instance)
|| speculativeNamespacesAndTypes.Contains(actualSymbol.Symbol, CandidateSymbolEqualityComparer.Instance);
}
foreach (var symbol in actualSymbol.CandidateSymbols)
{
if (!speculativeSymbols.Contains(symbol, CandidateSymbolEqualityComparer.Instance)
&& !speculativeNamespacesAndTypes.Contains(symbol, CandidateSymbolEqualityComparer.Instance))
{
return false;
}
}
return true;
}
///
/// Compares symbols by their original definition.
///
private sealed class CandidateSymbolEqualityComparer : IEqualityComparer
{
public static CandidateSymbolEqualityComparer Instance { get; } = new CandidateSymbolEqualityComparer();
private CandidateSymbolEqualityComparer()
{
}
public bool Equals(ISymbol x, ISymbol y)
{
if (x is null || y is null)
{
return x == y;
}
return x.OriginalDefinition.Equals(y.OriginalDefinition);
}
public int GetHashCode(ISymbol obj)
{
return obj?.OriginalDefinition.GetHashCode() ?? 0;
}
}
private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(MemberAccessExpressionSyntax memberAccess)
{
// We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression.
// However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering
// aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines
// etc. around the user's original code.
return memberAccess.GetLeadingTrivia()
.AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia())
.AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia())
.AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia())
.AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia());
}
private static IEnumerable WithoutElasticTrivia(this IEnumerable list)
{
return list.Where(t => !t.IsElastic());
}
public static bool InsideCrefReference(this ExpressionSyntax expression)
{
var crefAttribute = expression.FirstAncestorOrSelf();
return crefAttribute != null;
}
private static bool InsideNameOfExpression(ExpressionSyntax expression, SemanticModel semanticModel)
{
var nameOfInvocationExpr = expression.FirstAncestorOrSelf(
invocationExpr =>
{
return (invocationExpr.Expression is IdentifierNameSyntax identifierName) && (identifierName.Identifier.Text == "nameof") &&
semanticModel.GetConstantValue(invocationExpr).HasValue &&
(semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String);
});
return nameOfInvocationExpr != null;
}
private static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel)
{
return !IsInMemberAccessContext(name) &&
!InsideCrefReference(name) &&
!InsideNameOfExpression(name, semanticModel) &&
SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language);
}
private static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, OptionSet optionSet, SemanticModel semanticModel)
{
if (!SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, semanticModel.Language))
return false;
return (IsInMemberAccessContext(expression) || InsideCrefReference(expression)) &&
!InsideNameOfExpression(expression, semanticModel);
}
public static bool IsInMemberAccessContext(this ExpressionSyntax expression) =>
expression?.Parent is MemberAccessExpressionSyntax;
private static bool IsAliasReplaceableExpression(ExpressionSyntax expression)
{
while (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax currentMember))
{
expression = currentMember.Expression;
}
if (expression.Kind() == SyntaxKind.IdentifierName ||
expression.Kind() == SyntaxKind.QualifiedName ||
expression.Kind() == SyntaxKind.AliasQualifiedName ||
expression.Kind() == SyntaxKind.GenericName)
{
return true;
}
return false;
}
[PerformanceSensitive(
"https://github.com/dotnet/roslyn/issues/23582",
Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")]
private static bool TryReplaceExpressionWithAlias(ExpressionSyntax node, SemanticModel semanticModel, CancellationToken cancellationToken, out IAliasSymbol aliasReplacement)
{
aliasReplacement = null;
if (!IsAliasReplaceableExpression(node))
return false;
// Avoid the TryReplaceWithAlias algorithm if the tree has no using alias directives. Since the input node
// might be a speculative node (not fully rooted in a tree), we use the original semantic model to find the
// equivalent node in the original tree, and from there determine if the tree has any using alias
// directives.
var originalModel = semanticModel.GetOriginalSemanticModel();
// Perf: We are only using the syntax tree root in a fast-path syntax check. If the root is not readily
// available, it is fine to continue through the normal algorithm.
if (originalModel.SyntaxTree.TryGetRoot(out var root))
{
if (!HasUsingAliasDirective(root))
{
return false;
}
}
var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
// If the Symbol is a constructor get its containing type
if (symbol.IsConstructor())
{
symbol = symbol.ContainingType;
}
if (node is QualifiedNameSyntax || node is AliasQualifiedNameSyntax)
{
SyntaxAnnotation aliasAnnotationInfo = null;
// The following condition checks if the user has used alias in the original code and
// if so the expression is replaced with the Alias
if (node is QualifiedNameSyntax qualifiedNameNode)
{
if (qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind))
{
aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single();
}
}
if (node is AliasQualifiedNameSyntax aliasQualifiedNameNode)
{
if (aliasQualifiedNameNode.Name.Identifier.HasAnnotations(AliasAnnotation.Kind))
{
aliasAnnotationInfo = aliasQualifiedNameNode.Name.Identifier.GetAnnotations(AliasAnnotation.Kind).Single();
}
}
if (aliasAnnotationInfo != null)
{
var aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo);
var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName);
var aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace);
if (aliasTypeInfo != null)
{
aliasReplacement = aliasTypeInfo;
return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol);
}
}
}
if (node.Kind() == SyntaxKind.IdentifierName &&
semanticModel.GetAliasInfo((IdentifierNameSyntax)node, cancellationToken) != null)
{
return false;
}
// an alias can only replace a type or namespace
if (symbol == null ||
(symbol.Kind != SymbolKind.Namespace && symbol.Kind != SymbolKind.NamedType))
{
return false;
}
var preferAliasToQualifiedName = true;
if (node is QualifiedNameSyntax qualifiedName)
{
if (!qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation))
{
var type = semanticModel.GetTypeInfo(node, cancellationToken).Type;
if (type != null)
{
var keywordKind = GetPredefinedKeywordKind(type.SpecialType);
if (keywordKind != SyntaxKind.None)
{
preferAliasToQualifiedName = false;
}
}
}
}
if (node is AliasQualifiedNameSyntax aliasQualifiedNameSyntax)
{
if (!aliasQualifiedNameSyntax.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation))
{
var type = semanticModel.GetTypeInfo(node, cancellationToken).Type;
if (type != null)
{
var keywordKind = GetPredefinedKeywordKind(type.SpecialType);
if (keywordKind != SyntaxKind.None)
{
preferAliasToQualifiedName = false;
}
}
}
}
aliasReplacement = GetAliasForSymbol((INamespaceOrTypeSymbol)symbol, node.GetFirstToken(), semanticModel, cancellationToken);
if (aliasReplacement != null && preferAliasToQualifiedName)
{
return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol);
}
return false;
}
private static bool HasUsingAliasDirective(SyntaxNode syntax)
{
SyntaxList usings;
SyntaxList members;
if (syntax.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration))
{
usings = namespaceDeclaration.Usings;
members = namespaceDeclaration.Members;
}
else if (syntax.IsKind(SyntaxKind.CompilationUnit, out CompilationUnitSyntax compilationUnit))
{
usings = compilationUnit.Usings;
members = compilationUnit.Members;
}
else
{
return false;
}
foreach (var usingDirective in usings)
{
if (usingDirective.Alias != null)
{
return true;
}
}
foreach (var member in members)
{
if (HasUsingAliasDirective(member))
{
return true;
}
}
return false;
}
// We must verify that the alias actually binds back to the thing it's aliasing.
// It's possible there's another symbol with the same name as the alias that binds
// first
private static bool ValidateAliasForTarget(IAliasSymbol aliasReplacement, SemanticModel semanticModel, ExpressionSyntax node, ISymbol symbol)
{
var aliasName = aliasReplacement.Name;
// If we're the argument of a nameof(X.Y) call, then we can't simplify to an
// alias unless the alias has the same name as us (i.e. 'Y').
if (node.IsNameOfArgumentExpression())
{
var nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent);
if (!nameofValueOpt.HasValue)
{
return false;
}
if (nameofValueOpt.Value is string existingVal &&
existingVal != aliasName)
{
return false;
}
}
var boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name: aliasName);
if (boundSymbols.Length == 1)
{
if (boundSymbols[0] is IAliasSymbol boundAlias && aliasReplacement.Target.Equals(symbol))
{
return true;
}
}
return false;
}
public static bool IsNameOfArgumentExpression(this ExpressionSyntax expression)
{
return expression.IsParentKind(SyntaxKind.Argument) &&
expression.Parent.IsParentKind(SyntaxKind.ArgumentList) &&
expression.Parent.Parent.Parent is InvocationExpressionSyntax invocation &&
invocation.IsNameOfInvocation();
}
public static bool IsNameOfInvocation(this InvocationExpressionSyntax invocation)
{
return invocation.Expression is IdentifierNameSyntax identifierName &&
identifierName.Identifier.IsKindOrHasMatchingText(SyntaxKind.NameOfKeyword);
}
public static IAliasSymbol GetAliasForSymbol(INamespaceOrTypeSymbol symbol, SyntaxToken token, SemanticModel semanticModel, CancellationToken cancellationToken)
{
var originalSemanticModel = semanticModel.GetOriginalSemanticModel();
if (!originalSemanticModel.SyntaxTree.HasCompilationUnitRoot)
{
return null;
}
var namespaceId = GetNamespaceIdForAliasSearch(semanticModel, token, cancellationToken);
if (namespaceId < 0)
{
return null;
}
if (!AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId, symbol, out var aliasSymbol))
{
// add cache
AliasSymbolCache.AddAliasSymbols(originalSemanticModel, namespaceId, semanticModel.LookupNamespacesAndTypes(token.SpanStart).OfType());
// retry
AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId, symbol, out aliasSymbol);
}
return aliasSymbol;
}
private static SyntaxNode GetStartNodeForNamespaceId(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken)
{
if (!semanticModel.IsSpeculativeSemanticModel)
{
return token.Parent;
}
var originalSemanticMode = semanticModel.GetOriginalSemanticModel();
token = originalSemanticMode.SyntaxTree.GetRoot(cancellationToken).FindToken(semanticModel.OriginalPositionForSpeculation);
return token.Parent;
}
private static int GetNamespaceIdForAliasSearch(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken)
{
var startNode = GetStartNodeForNamespaceId(semanticModel, token, cancellationToken);
if (!startNode.SyntaxTree.HasCompilationUnitRoot)
{
return -1;
}
// NOTE: If we're currently in a block of usings, then we want to collect the
// aliases that are higher up than this block. Using aliases declared in a block of
// usings are not usable from within that same block.
var usingDirective = startNode.GetAncestorOrThis();
if (usingDirective != null)
{
startNode = usingDirective.Parent.Parent;
if (startNode == null)
{
return -1;
}
}
// check whether I am under a namespace
var @namespace = startNode.GetAncestorOrThis();
if (@namespace != null)
{
// since we have node inside of the root, root should be already there
// search for namespace id should be quite cheap since normally there should be
// only a few namespace defined in a source file if it is not 1. that is why it is
// not cached.
var startIndex = 1;
return GetNamespaceId(startNode.SyntaxTree.GetRoot(cancellationToken), @namespace, ref startIndex);
}
// no namespace, under compilation unit directly
return 0;
}
private static int GetNamespaceId(SyntaxNode container, NamespaceDeclarationSyntax target, ref int index)
{
if (container is CompilationUnitSyntax compilation)
{
return GetNamespaceId(compilation.Members, target, ref index);
}
if (container is NamespaceDeclarationSyntax @namespace)
{
return GetNamespaceId(@namespace.Members, target, ref index);
}
return Contract.FailWithReturn("shouldn't reach here");
}
private static int GetNamespaceId(SyntaxList members, NamespaceDeclarationSyntax target, ref int index)
{
foreach (var member in members)
{
if (!(member is NamespaceDeclarationSyntax childNamespace))
{
continue;
}
if (childNamespace == target)
{
return index;
}
index++;
var result = GetNamespaceId(childNamespace, target, ref index);
if (result > 0)
{
return result;
}
}
return -1;
}
private static bool TryReduceName(
NameSyntax name,
SemanticModel semanticModel,
out TypeSyntax replacementNode,
out TextSpan issueSpan,
OptionSet optionSet,
CancellationToken cancellationToken)
{
replacementNode = null;
issueSpan = default;
if (name.IsVar)
{
return false;
}
// we should not simplify a name of a namespace declaration
if (IsPartOfNamespaceDeclarationName(name))
{
return false;
}
// We can simplify Qualified names and AliasQualifiedNames. Generally, if we have
// something like "A.B.C.D", we only consider the full thing something we can simplify.
// However, in the case of "A.B.C<>.D", then we'll only consider simplifying up to the
// first open name. This is because if we remove the open name, we'll often change
// meaning as "D" will bind to C.D which is different than C<>.D!
if (name is QualifiedNameSyntax qualifiedName)
{
var left = qualifiedName.Left;
if (ContainsOpenName(left))
{
// Don't simplify A.B<>.C
return false;
}
}
// 1. see whether binding the name binds to a symbol/type. if not, it is ambiguous and
// nothing we can do here.
var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name);
if (symbol == null)
{
return false;
}
// treat constructor names as types
var method = symbol as IMethodSymbol;
if (method.IsConstructor())
{
symbol = method.ContainingType;
}
if (symbol.Kind == SymbolKind.Method && name.Kind() == SyntaxKind.GenericName)
{
var genericName = (GenericNameSyntax)name;
replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier)
.WithLeadingTrivia(genericName.GetLeadingTrivia())
.WithTrailingTrivia(genericName.GetTrailingTrivia());
issueSpan = genericName.TypeArgumentList.Span;
return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken);
}
if (!(symbol is INamespaceOrTypeSymbol))
{
return false;
}
if (name.HasAnnotations(SpecialTypeAnnotation.Kind))
{
replacementNode = SyntaxFactory.PredefinedType(
SyntaxFactory.Token(
name.GetLeadingTrivia(),
GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())),
name.GetTrailingTrivia()));
issueSpan = name.Span;
return name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel);
}
else
{
if (!name.IsRightSideOfDotOrColonColon())
{
if (TryReplaceExpressionWithAlias(name, semanticModel, cancellationToken, out var aliasReplacement))
{
// get the token text as it appears in source code to preserve e.g. Unicode character escaping
var text = aliasReplacement.Name;
var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault();
if (syntaxRef != null)
{
var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier;
text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString();
}
var identifierToken = SyntaxFactory.Identifier(
name.GetLeadingTrivia(),
SyntaxKind.IdentifierToken,
text,
aliasReplacement.Name,
name.GetTrailingTrivia());
identifierToken = CSharpSimplificationService.TryEscapeIdentifierToken(identifierToken, name, semanticModel);
replacementNode = SyntaxFactory.IdentifierName(identifierToken);
// Merge annotation to new syntax node
var annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind);
foreach (var annotatedNodeOrToken in annotatedNodesOrTokens)
{
if (annotatedNodeOrToken.IsToken)
{
identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken);
}
else
{
replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode);
}
}
annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind);
foreach (var annotatedNodeOrToken in annotatedNodesOrTokens)
{
if (annotatedNodeOrToken.IsToken)
{
identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken);
}
else
{
replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode);
}
}
replacementNode = ((SimpleNameSyntax)replacementNode).WithIdentifier(identifierToken);
issueSpan = name.Span;
// In case the alias name is the same as the last name of the alias target, we only include
// the left part of the name in the unnecessary span to Not confuse uses.
if (name.Kind() == SyntaxKind.QualifiedName)
{
var qualifiedName3 = (QualifiedNameSyntax)name;
if (qualifiedName3.Right.Identifier.ValueText == identifierToken.ValueText)
{
issueSpan = qualifiedName3.Left.Span;
}
}
// first check if this would be a valid reduction
if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel))
{
// in case this alias name ends with "Attribute", we're going to see if we can also
// remove that suffix.
if (TryReduceAttributeSuffix(
name,
identifierToken,
out var replacementNodeWithoutAttributeSuffix,
out var issueSpanWithoutAttributeSuffix))
{
if (CanReplaceWithReducedName(name, replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken))
{
replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix);
issueSpan = issueSpanWithoutAttributeSuffix;
}
}
return true;
}
return false;
}
var nameHasNoAlias = false;
if (name is SimpleNameSyntax simpleName)
{
if (!simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind))
{
nameHasNoAlias = true;
}
}
if (name is QualifiedNameSyntax qualifiedName2)
{
if (!qualifiedName2.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation))
{
nameHasNoAlias = true;
}
}
if (name is AliasQualifiedNameSyntax aliasQualifiedName)
{
if (aliasQualifiedName.Name is SimpleNameSyntax &&
!aliasQualifiedName.Name.Identifier.HasAnnotations(AliasAnnotation.Kind) &&
!aliasQualifiedName.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation))
{
nameHasNoAlias = true;
}
}
var aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken);
if (nameHasNoAlias && aliasInfo == null)
{
// Don't simplify to predefined type if name is part of a QualifiedName.
// QualifiedNames can't contain PredefinedTypeNames (although MemberAccessExpressions can).
// In other words, the left side of a QualifiedName can't be a PredefinedTypeName.
var inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, optionSet, semanticModel);
var inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, optionSet, semanticModel);
if (!name.Parent.IsKind(SyntaxKind.QualifiedName) && (inDeclarationContext || inMemberAccessContext))
{
var codeStyleOptionName = inDeclarationContext
? nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration)
: nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess);
var type = semanticModel.GetTypeInfo(name, cancellationToken).Type;
if (type != null)
{
var keywordKind = GetPredefinedKeywordKind(type.SpecialType);
if (keywordKind != SyntaxKind.None &&
CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName))
{
return true;
}
}
else
{
var typeSymbol = semanticModel.GetSymbolInfo(name, cancellationToken).Symbol;
if (typeSymbol.IsKind(SymbolKind.NamedType))
{
var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)typeSymbol).SpecialType);
if (keywordKind != SyntaxKind.None &&
CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName))
{
return true;
}
}
}
}
}
// Nullable rewrite: Nullable -> int?
// Don't rewrite in the case where Nullable is part of some qualified name like Nullable.Something
if (!name.IsVar && (symbol.Kind == SymbolKind.NamedType) && !name.IsLeftSideOfQualifiedName())
{
var type = (INamedTypeSymbol)symbol;
if (aliasInfo == null && CanSimplifyNullable(type, name, semanticModel))
{
GenericNameSyntax genericName;
if (name.Kind() == SyntaxKind.QualifiedName)
{
genericName = (GenericNameSyntax)((QualifiedNameSyntax)name).Right;
}
else
{
genericName = (GenericNameSyntax)name;
}
var oldType = genericName.TypeArgumentList.Arguments.First();
if (oldType.Kind() == SyntaxKind.OmittedTypeArgument)
{
return false;
}
replacementNode = SyntaxFactory.NullableType(oldType)
.WithLeadingTrivia(name.GetLeadingTrivia())
.WithTrailingTrivia(name.GetTrailingTrivia());
issueSpan = name.Span;
// we need to simplify the whole qualified name at once, because replacing the identifier on the left in
// System.Nullable alone would be illegal.
// If this fails we want to continue to try at least to remove the System if possible.
if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel))
{
return true;
}
}
}
}
SyntaxToken identifier;
switch (name.Kind())
{
case SyntaxKind.AliasQualifiedName:
var simpleName = ((AliasQualifiedNameSyntax)name).Name
.WithLeadingTrivia(name.GetLeadingTrivia());
simpleName = simpleName.ReplaceToken(simpleName.Identifier,
((AliasQualifiedNameSyntax)name).Name.Identifier.CopyAnnotationsTo(
simpleName.Identifier.WithLeadingTrivia(
((AliasQualifiedNameSyntax)name).Alias.Identifier.LeadingTrivia)));
replacementNode = simpleName;
issueSpan = ((AliasQualifiedNameSyntax)name).Alias.Span;
break;
case SyntaxKind.QualifiedName:
replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia());
issueSpan = ((QualifiedNameSyntax)name).Left.Span;
break;
case SyntaxKind.IdentifierName:
identifier = ((IdentifierNameSyntax)name).Identifier;
// we can try to remove the Attribute suffix if this is the attribute name
TryReduceAttributeSuffix(name, identifier, out replacementNode, out issueSpan);
break;
}
}
if (replacementNode == null)
{
return false;
}
// We may be looking at a name `X.Y` seeing if we can replace it with `Y`. However, in
// order to know for sure, we actually have to look slightly higher at `X.Y.Z` to see if
// it can simplify to `Y.Z`. This is because in the `Color Color` case we can only tell
// if we can reduce by looking by also looking at what comes next to see if it will
// cause the simplified name to bind to the instance or static side.
if (TryReduceCrefColorColor(name, replacementNode, semanticModel, cancellationToken))
{
return true;
}
return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken);
}
private static bool TryReduceCrefColorColor(
NameSyntax name, TypeSyntax replacement,
SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (!InsideCrefReference(name))
return false;
if (name.Parent is QualifiedCrefSyntax qualifiedCrefParent && qualifiedCrefParent.Container == name)
{
// we have and we're trying to see if we can replace
// A.B.C with C. In this case the parent of A.B.C is A.B.C.D which is a
// QualifiedCrefSyntax
var qualifiedReplacement = SyntaxFactory.QualifiedCref(replacement, qualifiedCrefParent.Member);
if (qualifiedCrefParent.TryReduceOrSimplifyQualifiedCref(
semanticModel, qualifiedReplacement, out _, out _, cancellationToken))
{
return true;
}
}
else if (name.Parent is QualifiedNameSyntax qualifiedParent && qualifiedParent.Left == name &&
replacement is NameSyntax replacementName)
{
// we have and we're trying to see if we can replace
// A.B with B. In this case the parent of A.B is A.B.C which is a
// QualifiedNameSyntax
var qualifiedReplacement = SyntaxFactory.QualifiedName(replacementName, qualifiedParent.Right);
return CanReplaceWithReducedName(
qualifiedParent, qualifiedReplacement, semanticModel, cancellationToken);
}
return false;
}
private static bool CanSimplifyNullable(INamedTypeSymbol type, NameSyntax name, SemanticModel semanticModel)
{
if (!type.IsNullable())
{
return false;
}
if (type.IsUnboundGenericType)
{
// Don't simplify unbound generic type "Nullable<>".
return false;
}
if (InsideNameOfExpression(name, semanticModel))
{
// Nullable can't be simplified to T? in nameof expressions.
return false;
}
if (!InsideCrefReference(name))
{
// Nullable can always be simplified to T? outside crefs.
return true;
}
if (name.Parent is NameMemberCrefSyntax)
return false;
// Inside crefs, if the T in this Nullable{T} is being declared right here
// then this Nullable{T} is not a constructed generic type and we should
// not offer to simplify this to T?.
//
// For example, we should not offer the simplification in the following cases where
// T does not bind to an existing type / type parameter in the user's code.
// -
// -
//
// And we should offer the simplification in the following cases where SomeType and
// SomeMethod bind to a type and method declared elsewhere in the users code.
// -
var argument = type.TypeArguments.SingleOrDefault();
if (argument == null || argument.IsErrorType())
{
return false;
}
var argumentDecl = argument.DeclaringSyntaxReferences.FirstOrDefault();
if (argumentDecl == null)
{
// The type argument is a type from metadata - so this is a constructed generic nullable type that can be simplified (e.g. Nullable(Of Integer)).
return true;
}
return !name.Span.Contains(argumentDecl.Span);
}
private static bool CanReplaceWithPredefinedTypeKeywordInContext(
NameSyntax name,
SemanticModel semanticModel,
out TypeSyntax replacementNode,
ref TextSpan issueSpan,
SyntaxKind keywordKind,
string codeStyleOptionName)
{
replacementNode = CreatePredefinedTypeSyntax(name, keywordKind);
issueSpan = name.Span; // we want to show the whole name expression as unnecessary
var canReduce = name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel);
if (canReduce)
{
replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName));
}
return canReduce;
}
private static TypeSyntax CreatePredefinedTypeSyntax(ExpressionSyntax expression, SyntaxKind keywordKind)
{
return SyntaxFactory.PredefinedType(SyntaxFactory.Token(expression.GetLeadingTrivia(), keywordKind, expression.GetTrailingTrivia()));
}
private static bool TryReduceAttributeSuffix(
NameSyntax name,
SyntaxToken identifierToken,
out TypeSyntax replacementNode,
out TextSpan issueSpan)
{
issueSpan = default;
replacementNode = default;
// we can try to remove the Attribute suffix if this is the attribute name
if (SyntaxFacts.IsAttributeName(name))
{
if (name.Parent.Kind() == SyntaxKind.Attribute || name.IsRightSideOfDotOrColonColon())
{
const string AttributeName = "Attribute";
// an attribute that should keep it (unnecessary "Attribute" suffix should be annotated with a DontSimplifyAnnotation
if (identifierToken.ValueText != AttributeName && identifierToken.ValueText.EndsWith(AttributeName, StringComparison.Ordinal) && !identifierToken.HasAnnotation(SimplificationHelpers.DontSimplifyAnnotation))
{
// weird. the semantic model is able to bind attribute syntax like "[as()]" although it's not valid code.
// so we need another check for keywords manually.
var newAttributeName = identifierToken.ValueText.Substring(0, identifierToken.ValueText.Length - 9);
if (SyntaxFacts.GetKeywordKind(newAttributeName) != SyntaxKind.None)
{
return false;
}
// if this attribute name in source contained Unicode escaping, we will loose it now
// because there is no easy way to determine the substring from identifier->ToString()
// which would be needed to pass to SyntaxFactory.Identifier
// The result is an unescaped Unicode character in source.
// once we remove the Attribute suffix, we can't use an escaped identifier
var newIdentifierToken = identifierToken.CopyAnnotationsTo(
SyntaxFactory.Identifier(
identifierToken.LeadingTrivia,
newAttributeName,
identifierToken.TrailingTrivia));
replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken)
.WithLeadingTrivia(name.GetLeadingTrivia());
issueSpan = new TextSpan(identifierToken.Span.End - 9, 9);
return true;
}
}
}
return false;
}
///
/// Checks if the SyntaxNode is a name of a namespace declaration. To be a namespace name, the syntax
/// must be parented by an namespace declaration and the node itself must be equal to the declaration's Name
/// property.
///
///
///
private static bool IsPartOfNamespaceDeclarationName(SyntaxNode node)
{
var parent = node;
while (parent != null)
{
switch (parent.Kind())
{
case SyntaxKind.IdentifierName:
case SyntaxKind.QualifiedName:
node = parent;
parent = parent.Parent;
break;
case SyntaxKind.NamespaceDeclaration:
var namespaceDeclaration = (NamespaceDeclarationSyntax)parent;
return object.Equals(namespaceDeclaration.Name, node);
default:
return false;
}
}
return false;
}
private static bool TrySimplify(
ExpressionSyntax expression,
SemanticModel semanticModel,
out ExpressionSyntax replacementNode,
out TextSpan issueSpan)
{
replacementNode = null;
issueSpan = default;
switch (expression.Kind())
{
case SyntaxKind.SimpleMemberAccessExpression:
{
var memberAccess = (MemberAccessExpressionSyntax)expression;
if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel))
{
return false;
}
if (TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, out var newLeft, out issueSpan))
{
// replacement node might not be in it's simplest form, so add simplify annotation to it.
replacementNode = memberAccess.Update(newLeft, memberAccess.OperatorToken, memberAccess.Name)
.WithAdditionalAnnotations(Simplifier.Annotation);
// Ensure that replacement doesn't change semantics.
return !ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel);
}
return false;
}
case SyntaxKind.QualifiedName:
{
var qualifiedName = (QualifiedNameSyntax)expression;
if (TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, out var newLeft, out issueSpan))
{
// replacement node might not be in it's simplest form, so add simplify annotation to it.
replacementNode = qualifiedName.Update((NameSyntax)newLeft, qualifiedName.DotToken, qualifiedName.Right)
.WithAdditionalAnnotations(Simplifier.Annotation);
// Ensure that replacement doesn't change semantics.
return !ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel);
}
return false;
}
}
return false;
}
private static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel)
{
var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None);
return speculationAnalyzer.ReplacementChangesSemantics();
}
// Note: The caller needs to verify that replacement doesn't change semantics of the original expression.
private static bool TrySimplifyMemberAccessOrQualifiedName(
ExpressionSyntax left,
ExpressionSyntax right,
SemanticModel semanticModel,
out ExpressionSyntax replacementNode,
out TextSpan issueSpan)
{
replacementNode = null;
issueSpan = default;
if (left != null && right != null)
{
var leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left);
if (leftSymbol != null && (leftSymbol.Kind == SymbolKind.NamedType))
{
var rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right);
if (rightSymbol != null && (rightSymbol.IsStatic || rightSymbol.Kind == SymbolKind.NamedType))
{
// Static member access or nested type member access.
var containingType = rightSymbol.ContainingType;
var enclosingSymbol = semanticModel.GetEnclosingSymbol(left.SpanStart);
var enclosingTypeParametersInsideOut = new List();
while (enclosingSymbol != null)
{
if (enclosingSymbol is IMethodSymbol methodSymbol)
{
if (methodSymbol.TypeArguments.Length != 0)
{
enclosingTypeParametersInsideOut.AddRange(methodSymbol.TypeArguments);
}
}
if (enclosingSymbol is INamedTypeSymbol namedTypeSymbol)
{
if (namedTypeSymbol.TypeArguments.Length != 0)
{
enclosingTypeParametersInsideOut.AddRange(namedTypeSymbol.TypeArguments);
}
}
enclosingSymbol = enclosingSymbol.ContainingSymbol;
}
if (containingType != null && !containingType.Equals(leftSymbol))
{
if (leftSymbol is INamedTypeSymbol namedType &&
containingType.TypeArguments.Length != 0)
{
return false;
}
// We have a static member access or a nested type member access using a more derived type.
// Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type.
replacementNode = containingType.GenerateTypeSyntax()
.WithLeadingTrivia(left.GetLeadingTrivia())
.WithTrailingTrivia(left.GetTrailingTrivia());
issueSpan = left.Span;
return true;
}
}
}
}
return false;
}
private static bool CanReplaceWithReducedName(
MemberAccessExpressionSyntax memberAccess,
ExpressionSyntax reducedName,
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel))
{
return false;
}
var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken);
if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() ||
speculationAnalyzer.ReplacementChangesSemantics())
{
return false;
}
if (WillConflictWithExistingLocal(memberAccess, reducedName, semanticModel))
{
return false;
}
if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel))
{
return false;
}
if (AccessMethodWithDynamicArgumentInsideStructConstructor(memberAccess, semanticModel))
{
return false;
}
if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression)
{
var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken);
var symbol = semanticModel.GetSymbolInfo(memberAccess.Name, cancellationToken).Symbol;
if (enclosingNamedType != null &&
!enclosingNamedType.IsSealed &&
symbol != null &&
symbol.IsOverridable())
{
return false;
}
}
var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess);
return !invalidTransformation1;
}
private static bool ParserWouldTreatExpressionAsCast(ExpressionSyntax reducedNode, MemberAccessExpressionSyntax originalNode)
{
SyntaxNode parent = originalNode;
while (parent != null)
{
if (parent.IsParentKind(SyntaxKind.SimpleMemberAccessExpression))
{
parent = parent.Parent;
continue;
}
if (!parent.IsParentKind(SyntaxKind.ParenthesizedExpression))
{
return false;
}
break;
}
var newExpression = parent.ReplaceNode(originalNode, reducedNode);
// detect cast ambiguities according to C# spec #7.7.6
if (IsNameOrMemberAccessButNoExpression(newExpression))
{
var nextToken = parent.Parent.GetLastToken().GetNextToken();
return nextToken.Kind() == SyntaxKind.OpenParenToken ||
nextToken.Kind() == SyntaxKind.TildeToken ||
nextToken.Kind() == SyntaxKind.ExclamationToken ||
(SyntaxFacts.IsKeywordKind(nextToken.Kind()) && !(nextToken.Kind() == SyntaxKind.AsKeyword || nextToken.Kind() == SyntaxKind.IsKeyword));
}
return false;
}
private static bool IsNameOrMemberAccessButNoExpression(SyntaxNode node)
{
if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
var memberAccess = (MemberAccessExpressionSyntax)node;
return memberAccess.Expression.IsKind(SyntaxKind.IdentifierName) ||
IsNameOrMemberAccessButNoExpression(memberAccess.Expression);
}
return node.IsKind(SyntaxKind.IdentifierName);
}
///
/// Tells if the Member access is the starting part of a Dynamic Invocation
///
///
///
/// Return true, if the member access is the starting point of a Dynamic Invocation
private static bool IsMemberAccessADynamicInvocation(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel)
{
var ancestorInvocation = memberAccess.FirstAncestorOrSelf();
if (ancestorInvocation != null && ancestorInvocation.SpanStart == memberAccess.SpanStart)
{
var typeInfo = semanticModel.GetTypeInfo(ancestorInvocation);
if (typeInfo.Type != null &&
typeInfo.Type.Kind == SymbolKind.DynamicType)
{
return true;
}
}
return false;
}
/*
* Name Reduction, to implicitly mean "this", is possible only after the initialization of all member variables but
* since the check for initialization of all member variable is a lot of work for this simplification we don't simplify
* even if all the member variables are initialized
*/
private static bool AccessMethodWithDynamicArgumentInsideStructConstructor(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel)
{
var constructor = memberAccess.Ancestors().OfType().SingleOrDefault();
if (constructor == null || constructor.Parent.Kind() != SyntaxKind.StructDeclaration)
{
return false;
}
return semanticModel.GetSymbolInfo(memberAccess.Name).CandidateReason == CandidateReason.LateBound;
}
private static bool CanReplaceWithReducedName(NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken)
{
var speculationAnalyzer = new SpeculationAnalyzer(name, reducedName, semanticModel, cancellationToken);
if (speculationAnalyzer.ReplacementChangesSemantics())
{
return false;
}
return CanReplaceWithReducedNameInContext(name, reducedName, semanticModel);
}
private static bool CanReplaceWithReducedNameInContext(
this NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel)
{
// Check for certain things that would prevent us from reducing this name in this context.
// For example, you can simplify "using a = System.Int32" to "using a = int" as it's simply
// not allowed in the C# grammar.
if (IsNonNameSyntaxInUsingDirective(name, reducedName) ||
WillConflictWithExistingLocal(name, reducedName, semanticModel) ||
IsAmbiguousCast(name, reducedName) ||
IsNullableTypeInPointerExpression(reducedName) ||
IsNotNullableReplaceable(name, reducedName) ||
IsNonReducableQualifiedNameInUsingDirective(semanticModel, name, reducedName))
{
return false;
}
return true;
}
private static bool IsNonReducableQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name, TypeSyntax reducedName)
{
// Whereas most of the time we do not want to reduce namespace names, We will
// make an exception for namespaces with the global:: alias.
return IsQualifiedNameInUsingDirective(model, name) &&
!IsGlobalAliasQualifiedName(name);
}
private static bool IsQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name)
{
while (name.IsLeftSideOfQualifiedName())
{
name = (NameSyntax)name.Parent;
}
if (name.IsParentKind(SyntaxKind.UsingDirective) &&
((UsingDirectiveSyntax)name.Parent).Alias == null)
{
// We're a qualified name in a using. We don't want to reduce this name as people like
// fully qualified names in usings so they can properly tell what the name is resolving
// to.
// However, if this name is actually referencing the special Script class, then we do
// want to allow that to be reduced.
return !IsInScriptClass(model, name);
}
return false;
}
private static bool IsGlobalAliasQualifiedName(NameSyntax name)
{
// Checks whether the `global::` alias is applied to the name
return name is AliasQualifiedNameSyntax aliasName &&
aliasName.Alias.Identifier.IsKind(SyntaxKind.GlobalKeyword);
}
private static bool IsInScriptClass(SemanticModel model, NameSyntax name)
{
var symbol = model.GetSymbolInfo(name).Symbol as INamedTypeSymbol;
while (symbol != null)
{
if (symbol.IsScriptClass)
{
return true;
}
symbol = symbol.ContainingType;
}
return false;
}
private static bool IsNotNullableReplaceable(NameSyntax name, TypeSyntax reducedName)
{
var isNotNullableReplaceable = false;
var isLeftSideOfDot = name.IsLeftSideOfDot();
var isRightSideOfDot = name.IsRightSideOfDot();
if (reducedName.Kind() == SyntaxKind.NullableType)
{
if (((NullableTypeSyntax)reducedName).ElementType.Kind() == SyntaxKind.OmittedTypeArgument)
{
isNotNullableReplaceable = true;
}
else
{
isNotNullableReplaceable = name.IsLeftSideOfDot() || name.IsRightSideOfDot();
}
}
return isNotNullableReplaceable;
}
private static bool IsThisOrTypeOrNamespace(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel)
{
if (memberAccess.Expression.Kind() == SyntaxKind.ThisExpression)
{
var previousToken = memberAccess.Expression.GetFirstToken().GetPreviousToken();
var symbol = semanticModel.GetSymbolInfo(memberAccess.Name).Symbol;
if (previousToken.Kind() == SyntaxKind.OpenParenToken &&
previousToken.Parent.IsKind(SyntaxKind.ParenthesizedExpression) &&
!previousToken.Parent.IsParentKind(SyntaxKind.ParenthesizedExpression) &&
((ParenthesizedExpressionSyntax)previousToken.Parent).Expression.Kind() == SyntaxKind.SimpleMemberAccessExpression &&
symbol != null && symbol.Kind == SymbolKind.Method)
{
return false;
}
return true;
}
var expressionInfo = semanticModel.GetSymbolInfo(memberAccess.Expression);
if (SimplificationHelpers.IsValidSymbolInfo(expressionInfo.Symbol))
{
if (expressionInfo.Symbol is INamespaceOrTypeSymbol)
{
return true;
}
if (expressionInfo.Symbol.IsThisParameter())
{
return true;
}
}
return false;
}
private static bool ContainsOpenName(NameSyntax name)
{
if (name is QualifiedNameSyntax qualifiedName)
{
return ContainsOpenName(qualifiedName.Left) || ContainsOpenName(qualifiedName.Right);
}
else if (name is GenericNameSyntax genericName)
{
return genericName.IsUnboundGenericName;
}
else
{
return false;
}
}
private static bool IsNullableTypeInPointerExpression(ExpressionSyntax simplifiedNode)
{
// Note: nullable type syntax is not allowed in pointer type syntax
if (simplifiedNode.Kind() == SyntaxKind.NullableType &&
simplifiedNode.DescendantNodes().Any(n => n is PointerTypeSyntax))
{
return true;
}
return false;
}
private static bool IsNonNameSyntaxInUsingDirective(ExpressionSyntax expression, ExpressionSyntax simplifiedNode)
{
return
expression.IsParentKind(SyntaxKind.UsingDirective) &&
!(simplifiedNode is NameSyntax);
}
private static bool WillConflictWithExistingLocal(
ExpressionSyntax expression, ExpressionSyntax simplifiedNode, SemanticModel semanticModel)
{
if (simplifiedNode is IdentifierNameSyntax identifierName &&
!SyntaxFacts.IsInNamespaceOrTypeContext(expression))
{
var symbols = semanticModel.LookupSymbols(expression.SpanStart, name: identifierName.Identifier.ValueText);
return symbols.Any(s => s is ILocalSymbol);
}
return false;
}
private static bool IsAmbiguousCast(ExpressionSyntax expression, ExpressionSyntax simplifiedNode)
{
// Can't simplify a type name in a cast expression if it would then cause the cast to be
// parsed differently. For example: (Goo::Bar)+1 is a cast. But if that simplifies to
// (Bar)+1 then that's an arithmetic expression.
if (expression.IsParentKind(SyntaxKind.CastExpression))
{
var castExpression = (CastExpressionSyntax)expression.Parent;
if (castExpression.Type == expression)
{
var newCastExpression = castExpression.ReplaceNode(castExpression.Type, simplifiedNode);
var reparsedCastExpression = SyntaxFactory.ParseExpression(newCastExpression.ToString());
if (!reparsedCastExpression.IsKind(SyntaxKind.CastExpression))
{
return true;
}
}
}
return false;
}
private static SyntaxNode FindImmediatelyEnclosingLocalVariableDeclarationSpace(SyntaxNode syntax)
{
for (var declSpace = syntax; declSpace != null; declSpace = declSpace.Parent)
{
switch (declSpace.Kind())
{
// These are declaration-space-defining syntaxes, by the spec:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.OperatorDeclaration:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.Block:
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.AnonymousMethodExpression:
case SyntaxKind.SwitchStatement:
case SyntaxKind.ForEachKeyword:
case SyntaxKind.ForStatement:
case SyntaxKind.UsingStatement:
// SPEC VIOLATION: We also want to stop walking out if, say, we are in a field
// initializer. Technically according to the wording of the spec it should be
// legal to use a simple name inconsistently inside a field initializer because
// it does not define a local variable declaration space. In practice of course
// we want to check for that. (As the native compiler does as well.)
case SyntaxKind.FieldDeclaration:
return declSpace;
}
}
return null;
}
///
/// Returns the predefined keyword kind for a given .
///
/// The of this type.
/// The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type.
public static SyntaxKind GetPredefinedKeywordKind(SpecialType specialType)
=> specialType switch
{
SpecialType.System_Boolean => SyntaxKind.BoolKeyword,
SpecialType.System_Byte => SyntaxKind.ByteKeyword,
SpecialType.System_SByte => SyntaxKind.SByteKeyword,
SpecialType.System_Int32 => SyntaxKind.IntKeyword,
SpecialType.System_UInt32 => SyntaxKind.UIntKeyword,
SpecialType.System_Int16 => SyntaxKind.ShortKeyword,
SpecialType.System_UInt16 => SyntaxKind.UShortKeyword,
SpecialType.System_Int64 => SyntaxKind.LongKeyword,
SpecialType.System_UInt64 => SyntaxKind.ULongKeyword,
SpecialType.System_Single => SyntaxKind.FloatKeyword,
SpecialType.System_Double => SyntaxKind.DoubleKeyword,
SpecialType.System_Decimal => SyntaxKind.DecimalKeyword,
SpecialType.System_String => SyntaxKind.StringKeyword,
SpecialType.System_Char => SyntaxKind.CharKeyword,
SpecialType.System_Object => SyntaxKind.ObjectKeyword,
SpecialType.System_Void => SyntaxKind.VoidKeyword,
_ => SyntaxKind.None,
};
public static SimpleNameSyntax GetRightmostName(this ExpressionSyntax node)
{
if (node is MemberAccessExpressionSyntax memberAccess && memberAccess.Name != null)
{
return memberAccess.Name;
}
if (node is QualifiedNameSyntax qualified && qualified.Right != null)
{
return qualified.Right;
}
if (node is SimpleNameSyntax simple)
{
return simple;
}
if (node is ConditionalAccessExpressionSyntax conditional)
{
return conditional.WhenNotNull.GetRightmostName();
}
if (node is MemberBindingExpressionSyntax memberBinding)
{
return memberBinding.Name;
}
if (node is AliasQualifiedNameSyntax aliasQualifiedName && aliasQualifiedName.Name != null)
{
return aliasQualifiedName.Name;
}
return null;
}
public static OperatorPrecedence GetOperatorPrecedence(this ExpressionSyntax expression)
{
switch (expression.Kind())
{
case SyntaxKind.SimpleMemberAccessExpression:
case SyntaxKind.ConditionalAccessExpression:
case SyntaxKind.InvocationExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PostIncrementExpression:
case SyntaxKind.PostDecrementExpression:
case SyntaxKind.ObjectCreationExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.DefaultExpression:
case SyntaxKind.CheckedExpression:
case SyntaxKind.UncheckedExpression:
case SyntaxKind.AnonymousMethodExpression:
// unsafe code
case SyntaxKind.SizeOfExpression:
case SyntaxKind.PointerMemberAccessExpression:
// From C# spec, 7.3.1:
// Primary: x.y x?.y x?[y] f(x) a[x] x++ x-- new typeof default checked unchecked delegate
return OperatorPrecedence.Primary;
case SyntaxKind.UnaryPlusExpression:
case SyntaxKind.UnaryMinusExpression:
case SyntaxKind.LogicalNotExpression:
case SyntaxKind.BitwiseNotExpression:
case SyntaxKind.PreIncrementExpression:
case SyntaxKind.PreDecrementExpression:
case SyntaxKind.CastExpression:
case SyntaxKind.AwaitExpression:
// unsafe code.
case SyntaxKind.PointerIndirectionExpression:
case SyntaxKind.AddressOfExpression:
// From C# spec, 7.3.1:
// Unary: + - ! ~ ++x --x (T)x await Task
return OperatorPrecedence.Unary;
case SyntaxKind.MultiplyExpression:
case SyntaxKind.DivideExpression:
case SyntaxKind.ModuloExpression:
// From C# spec, 7.3.1:
// Multiplicative: * / %
return OperatorPrecedence.Multiplicative;
case SyntaxKind.AddExpression:
case SyntaxKind.SubtractExpression:
// From C# spec, 7.3.1:
// Additive: + -
return OperatorPrecedence.Additive;
case SyntaxKind.LeftShiftExpression:
case SyntaxKind.RightShiftExpression:
// From C# spec, 7.3.1:
// Shift: << >>
return OperatorPrecedence.Shift;
case SyntaxKind.LessThanExpression:
case SyntaxKind.GreaterThanExpression:
case SyntaxKind.LessThanOrEqualExpression:
case SyntaxKind.GreaterThanOrEqualExpression:
case SyntaxKind.IsExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.IsPatternExpression:
// From C# spec, 7.3.1:
// Relational and type testing: < > <= >= is as
return OperatorPrecedence.RelationalAndTypeTesting;
case SyntaxKind.EqualsExpression:
case SyntaxKind.NotEqualsExpression:
// From C# spec, 7.3.1:
// Equality: == !=
return OperatorPrecedence.Equality;
case SyntaxKind.BitwiseAndExpression:
// From C# spec, 7.3.1:
// Logical AND: &
return OperatorPrecedence.LogicalAnd;
case SyntaxKind.ExclusiveOrExpression:
// From C# spec, 7.3.1:
// Logical XOR: ^
return OperatorPrecedence.LogicalXor;
case SyntaxKind.BitwiseOrExpression:
// From C# spec, 7.3.1:
// Logical OR: |
return OperatorPrecedence.LogicalOr;
case SyntaxKind.LogicalAndExpression:
// From C# spec, 7.3.1:
// Conditional AND: &&
return OperatorPrecedence.ConditionalAnd;
case SyntaxKind.LogicalOrExpression:
// From C# spec, 7.3.1:
// Conditional AND: ||
return OperatorPrecedence.ConditionalOr;
case SyntaxKind.CoalesceExpression:
// From C# spec, 7.3.1:
// Null coalescing: ??
return OperatorPrecedence.NullCoalescing;
case SyntaxKind.ConditionalExpression:
// From C# spec, 7.3.1:
// Conditional: ?:
return OperatorPrecedence.Conditional;
case SyntaxKind.SimpleAssignmentExpression:
case SyntaxKind.MultiplyAssignmentExpression:
case SyntaxKind.DivideAssignmentExpression:
case SyntaxKind.ModuloAssignmentExpression:
case SyntaxKind.AddAssignmentExpression:
case SyntaxKind.SubtractAssignmentExpression:
case SyntaxKind.LeftShiftAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.AndAssignmentExpression:
case SyntaxKind.ExclusiveOrAssignmentExpression:
case SyntaxKind.OrAssignmentExpression:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedLambdaExpression:
// From C# spec, 7.3.1:
// Conditional: ?:
return OperatorPrecedence.AssignmentAndLambdaExpression;
default:
return OperatorPrecedence.None;
}
}
public static bool TryConvertToStatement(
this ExpressionSyntax expression,
SyntaxToken? semicolonTokenOpt,
bool createReturnStatementForExpression,
out StatementSyntax statement)
{
// It's tricky to convert an arrow expression with directives over to a block.
// We'd need to find and remove the directives *after* the arrow expression and
// move them accordingly. So, for now, we just disallow this.
if (expression.GetLeadingTrivia().Any(t => t.IsDirective))
{
statement = null;
return false;
}
var semicolonToken = semicolonTokenOpt ?? SyntaxFactory.Token(SyntaxKind.SemicolonToken);
statement = ConvertToStatement(expression, semicolonToken, createReturnStatementForExpression);
return true;
}
private static StatementSyntax ConvertToStatement(ExpressionSyntax expression, SyntaxToken semicolonToken, bool createReturnStatementForExpression)
{
if (expression.IsKind(SyntaxKind.ThrowExpression))
{
var throwExpression = (ThrowExpressionSyntax)expression;
return SyntaxFactory.ThrowStatement(throwExpression.ThrowKeyword, throwExpression.Expression, semicolonToken);
}
else if (createReturnStatementForExpression)
{
if (expression.GetLeadingTrivia().Any(t => t.IsSingleOrMultiLineComment()))
{
return SyntaxFactory.ReturnStatement(expression.WithLeadingTrivia(SyntaxFactory.ElasticSpace))
.WithSemicolonToken(semicolonToken)
.WithLeadingTrivia(expression.GetLeadingTrivia())
.WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker);
}
else
{
return SyntaxFactory.ReturnStatement(expression)
.WithSemicolonToken(semicolonToken);
}
}
else
{
return SyntaxFactory.ExpressionStatement(expression)
.WithSemicolonToken(semicolonToken);
}
}
}
}