|
// 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.
#nullable disable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Xml.Serialization;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Utilities;
/// <summary>
/// Helper class to analyze the semantic effects of a speculated syntax node replacement on the parenting nodes.
/// Given an expression node from a syntax tree and a new expression from a different syntax tree,
/// it replaces the expression with the new expression to create a speculated syntax tree.
/// It uses the original tree's semantic model to create a speculative semantic model and verifies that
/// the syntax replacement doesn't break the semantics of any parenting nodes of the original expression.
/// </summary>
internal class SpeculationAnalyzer : AbstractSpeculationAnalyzer<
ExpressionSyntax,
TypeSyntax,
AttributeSyntax,
ArgumentSyntax,
CommonForEachStatementSyntax,
ThrowStatementSyntax,
InvocationExpressionSyntax,
Conversion>
{
/// <summary>
/// Creates a semantic analyzer for speculative syntax replacement.
/// </summary>
/// <param name="expression">Original expression to be replaced.</param>
/// <param name="newExpression">New expression to replace the original expression.</param>
/// <param name="semanticModel">Semantic model of <paramref name="expression"/> node's syntax tree.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <param name="skipVerificationForReplacedNode">
/// True if semantic analysis should be skipped for the replaced node and performed starting from parent of the original and replaced nodes.
/// This could be the case when custom verifications are required to be done by the caller or
/// semantics of the replaced expression are different from the original expression.
/// </param>
/// <param name="failOnOverloadResolutionFailuresInOriginalCode">
/// True if semantic analysis should fail when any of the invocation expression ancestors of <paramref name="expression"/> in original code has overload resolution failures.
/// </param>
public SpeculationAnalyzer(
ExpressionSyntax expression,
ExpressionSyntax newExpression,
SemanticModel semanticModel,
CancellationToken cancellationToken,
bool skipVerificationForReplacedNode = false,
bool failOnOverloadResolutionFailuresInOriginalCode = false)
: base(expression, newExpression, semanticModel, cancellationToken, skipVerificationForReplacedNode, failOnOverloadResolutionFailuresInOriginalCode)
{
}
protected override CodeAnalysis.LanguageService.ISyntaxFacts SyntaxFactsService { get; } = CSharpSyntaxFacts.Instance;
protected override bool CanAccessInstanceMemberThrough(ExpressionSyntax expression)
=> expression.Kind() is SyntaxKind.ThisExpression or SyntaxKind.BaseExpression;
protected override SyntaxNode GetSemanticRootForSpeculation(ExpressionSyntax expression)
{
Debug.Assert(expression != null);
SyntaxNode previousNode = null;
SyntaxNode parentNodeToSpeculate = null;
foreach (var node in expression.AncestorsAndSelf(ascendOutOfTrivia: false))
{
if (CanSpeculateOnNode(node))
{
// Only speculate on PrimaryConstructorBaseTypeSyntax if we are inside the argument list
if (node.Kind() is not SyntaxKind.PrimaryConstructorBaseType ||
previousNode.Kind() is SyntaxKind.ArgumentList)
{
parentNodeToSpeculate = node;
}
}
previousNode = node;
}
return parentNodeToSpeculate ?? expression;
}
public static bool CanSpeculateOnNode(SyntaxNode node)
=> node is StatementSyntax(kind: not SyntaxKind.Block) or TypeSyntax or CrefSyntax ||
node.Kind() is SyntaxKind.Attribute or
SyntaxKind.ThisConstructorInitializer or
SyntaxKind.BaseConstructorInitializer or
SyntaxKind.EqualsValueClause or
SyntaxKind.ArrowExpressionClause or
SyntaxKind.PrimaryConstructorBaseType;
protected override void ValidateSpeculativeSemanticModel(SemanticModel speculativeSemanticModel, SyntaxNode nodeToSpeculate)
{
Debug.Assert(speculativeSemanticModel != null ||
nodeToSpeculate is ExpressionSyntax ||
this.SemanticRootOfOriginalExpression.GetAncestors().Any(
node => node.Kind() is SyntaxKind.UnknownAccessorDeclaration or SyntaxKind.IncompleteMember or SyntaxKind.BracketedArgumentList),
"SemanticModel.TryGetSpeculativeSemanticModel() API returned false.");
}
protected override SemanticModel CreateSpeculativeSemanticModel(SyntaxNode originalNode, SyntaxNode nodeToSpeculate, SemanticModel semanticModel)
=> CreateSpeculativeSemanticModelForNode(originalNode, nodeToSpeculate, semanticModel);
public static SemanticModel CreateSpeculativeSemanticModelForNode(SyntaxNode originalNode, SyntaxNode nodeToSpeculate, SemanticModel semanticModel)
{
var position = originalNode.SpanStart;
var isInNamespaceOrTypeContext = SyntaxFacts.IsInNamespaceOrTypeContext(originalNode as ExpressionSyntax);
return CreateSpeculativeSemanticModelForNode(nodeToSpeculate, semanticModel, position, isInNamespaceOrTypeContext);
}
public static SemanticModel CreateSpeculativeSemanticModelForNode(SyntaxNode nodeToSpeculate, SemanticModel semanticModel, int position, bool isInNamespaceOrTypeContext)
{
if (semanticModel.IsSpeculativeSemanticModel)
{
// Chaining speculative model not supported, speculate off the original model.
Debug.Assert(semanticModel.ParentModel != null);
Debug.Assert(!semanticModel.ParentModel.IsSpeculativeSemanticModel);
position = semanticModel.OriginalPositionForSpeculation;
semanticModel = semanticModel.ParentModel;
}
SemanticModel speculativeModel;
if (nodeToSpeculate is StatementSyntax statementNode)
{
semanticModel.TryGetSpeculativeSemanticModel(position, statementNode, out speculativeModel);
return speculativeModel;
}
if (nodeToSpeculate is TypeSyntax typeNode)
{
var bindingOption = isInNamespaceOrTypeContext
? SpeculativeBindingOption.BindAsTypeOrNamespace
: SpeculativeBindingOption.BindAsExpression;
semanticModel.TryGetSpeculativeSemanticModel(position, typeNode, out speculativeModel, bindingOption);
return speculativeModel;
}
if (nodeToSpeculate is CrefSyntax cref)
{
semanticModel.TryGetSpeculativeSemanticModel(position, cref, out speculativeModel);
return speculativeModel;
}
switch (nodeToSpeculate.Kind())
{
case SyntaxKind.Attribute:
semanticModel.TryGetSpeculativeSemanticModel(position, (AttributeSyntax)nodeToSpeculate, out speculativeModel);
return speculativeModel;
case SyntaxKind.BaseConstructorInitializer:
case SyntaxKind.ThisConstructorInitializer:
semanticModel.TryGetSpeculativeSemanticModel(position, (ConstructorInitializerSyntax)nodeToSpeculate, out speculativeModel);
return speculativeModel;
case SyntaxKind.EqualsValueClause:
semanticModel.TryGetSpeculativeSemanticModel(position, (EqualsValueClauseSyntax)nodeToSpeculate, out speculativeModel);
return speculativeModel;
case SyntaxKind.ArrowExpressionClause:
semanticModel.TryGetSpeculativeSemanticModel(position, (ArrowExpressionClauseSyntax)nodeToSpeculate, out speculativeModel);
return speculativeModel;
case SyntaxKind.PrimaryConstructorBaseType:
semanticModel.TryGetSpeculativeSemanticModel(position, (PrimaryConstructorBaseTypeSyntax)nodeToSpeculate, out speculativeModel);
return speculativeModel;
}
// CONSIDER: Do we care about this case?
Debug.Assert(nodeToSpeculate is ExpressionSyntax);
return null;
}
/// <summary>
/// Determines whether performing the syntax replacement in one of the sibling nodes of the given lambda expressions will change the lambda binding semantics.
/// This is done by first determining the lambda parameters whose type differs in the replaced lambda node.
/// For each of these parameters, we find the descendant identifier name nodes in the lambda body and check if semantics of any of the parenting nodes of these
/// identifier nodes have changed in the replaced lambda.
/// </summary>
public bool ReplacementChangesSemanticsOfUnchangedLambda(ExpressionSyntax originalLambda, ExpressionSyntax replacedLambda)
{
originalLambda = originalLambda.WalkDownParentheses();
replacedLambda = replacedLambda.WalkDownParentheses();
SyntaxNode originalLambdaBody, replacedLambdaBody;
List<string> paramNames;
switch (originalLambda.Kind())
{
case SyntaxKind.ParenthesizedLambdaExpression:
{
var originalParenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)originalLambda;
var originalParams = originalParenthesizedLambda.ParameterList.Parameters;
if (!originalParams.Any())
{
return false;
}
var replacedParenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)replacedLambda;
var replacedParams = replacedParenthesizedLambda.ParameterList.Parameters;
Debug.Assert(originalParams.Count == replacedParams.Count);
paramNames = [];
for (var i = 0; i < originalParams.Count; i++)
{
var originalParam = originalParams[i];
var replacedParam = replacedParams[i];
if (!HaveSameParameterType(originalParam, replacedParam))
{
paramNames.Add(originalParam.Identifier.ValueText);
}
}
if (!paramNames.Any())
{
return false;
}
originalLambdaBody = originalParenthesizedLambda.Body;
replacedLambdaBody = replacedParenthesizedLambda.Body;
break;
}
case SyntaxKind.SimpleLambdaExpression:
{
var originalSimpleLambda = (SimpleLambdaExpressionSyntax)originalLambda;
var replacedSimpleLambda = (SimpleLambdaExpressionSyntax)replacedLambda;
if (HaveSameParameterType(originalSimpleLambda.Parameter, replacedSimpleLambda.Parameter))
{
return false;
}
paramNames = [originalSimpleLambda.Parameter.Identifier.ValueText];
originalLambdaBody = originalSimpleLambda.Body;
replacedLambdaBody = replacedSimpleLambda.Body;
break;
}
default:
throw ExceptionUtilities.UnexpectedValue(originalLambda.Kind());
}
var originalIdentifierNodes = originalLambdaBody.DescendantNodes().OfType<IdentifierNameSyntax>().Where(node => paramNames.Contains(node.Identifier.ValueText));
if (!originalIdentifierNodes.Any())
{
return false;
}
var replacedIdentifierNodes = replacedLambdaBody.DescendantNodes().OfType<IdentifierNameSyntax>().Where(node => paramNames.Contains(node.Identifier.ValueText));
return ReplacementChangesSemanticsForNodes(originalIdentifierNodes, replacedIdentifierNodes, originalLambdaBody);
}
private bool HaveSameParameterType(ParameterSyntax originalParam, ParameterSyntax replacedParam)
{
var originalParamType = this.OriginalSemanticModel.GetDeclaredSymbol(originalParam).Type;
var replacedParamType = this.SpeculativeSemanticModel.GetDeclaredSymbol(replacedParam).Type;
return Equals(originalParamType, replacedParamType);
}
private bool ReplacementChangesSemanticsForNodes(
IEnumerable<IdentifierNameSyntax> originalIdentifierNodes,
IEnumerable<IdentifierNameSyntax> replacedIdentifierNodes,
SyntaxNode originalRoot)
{
Debug.Assert(originalIdentifierNodes.Any());
Debug.Assert(originalIdentifierNodes.Count() == replacedIdentifierNodes.Count());
var originalChildNodeEnum = originalIdentifierNodes.GetEnumerator();
var replacedChildNodeEnum = replacedIdentifierNodes.GetEnumerator();
while (originalChildNodeEnum.MoveNext())
{
replacedChildNodeEnum.MoveNext();
if (ReplacementChangesSemantics(originalChildNodeEnum.Current, replacedChildNodeEnum.Current, originalRoot, skipVerificationForCurrentNode: true))
{
return true;
}
}
return false;
}
protected override bool ReplacementChangesSemanticsForNodeLanguageSpecific(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode, SyntaxNode previousOriginalNode, SyntaxNode previousReplacedNode)
{
Debug.Assert(previousOriginalNode == null || previousOriginalNode.Parent == currentOriginalNode);
Debug.Assert(previousReplacedNode == null || previousReplacedNode.Parent == currentReplacedNode);
if (currentOriginalNode.Kind() is SyntaxKind.CaseSwitchLabel or SyntaxKind.ConstantPattern)
{
var expression = (ExpressionSyntax)currentReplacedNode.ChildNodes().First();
if (expression.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression))
{
// We can't have a default literal inside a case label or constant pattern.
return true;
}
}
if (currentOriginalNode is BinaryExpressionSyntax binaryExpression)
{
// If replacing the node will result in a broken binary expression, we won't remove it.
return ReplacementBreaksBinaryExpression(binaryExpression, (BinaryExpressionSyntax)currentReplacedNode);
}
else if (currentOriginalNode.Kind() == SyntaxKind.LogicalNotExpression)
{
return !TypesAreCompatible(((PrefixUnaryExpressionSyntax)currentOriginalNode).Operand, ((PrefixUnaryExpressionSyntax)currentReplacedNode).Operand);
}
else if (currentOriginalNode.Kind() == SyntaxKind.ConditionalAccessExpression)
{
return ReplacementBreaksConditionalAccessExpression((ConditionalAccessExpressionSyntax)currentOriginalNode, (ConditionalAccessExpressionSyntax)currentReplacedNode);
}
else if (currentOriginalNode is AssignmentExpressionSyntax assignment)
{
// If replacing the node will result in a broken assignment expression, we won't remove it.
return ReplacementBreaksAssignmentExpression(assignment, (AssignmentExpressionSyntax)currentReplacedNode);
}
else if (currentOriginalNode is SelectOrGroupClauseSyntax or OrderingSyntax)
{
return !SymbolsAreCompatible(currentOriginalNode, currentReplacedNode);
}
else if (currentOriginalNode is QueryClauseSyntax queryClause)
{
return ReplacementBreaksQueryClause(queryClause, (QueryClauseSyntax)currentReplacedNode);
}
else if (currentOriginalNode.Kind() == SyntaxKind.VariableDeclarator)
{
// Heuristic: If replacing the node will result in changing the type of a local variable
// that is type-inferred, we won't remove it. It's possible to do this analysis, but it's
// very expensive and the benefit to the user is small.
var originalDeclarator = (VariableDeclaratorSyntax)currentOriginalNode;
var newDeclarator = (VariableDeclaratorSyntax)currentReplacedNode;
if (originalDeclarator.Initializer == null)
{
return newDeclarator.Initializer != null;
}
else if (newDeclarator.Initializer == null)
{
return true;
}
if (!originalDeclarator.Initializer.IsMissing &&
originalDeclarator.IsTypeInferred(this.OriginalSemanticModel) &&
!TypesAreCompatible(originalDeclarator.Initializer.Value, newDeclarator.Initializer.Value))
{
return true;
}
return false;
}
else if (currentOriginalNode is ConditionalExpressionSyntax originalExpression)
{
var newExpression = (ConditionalExpressionSyntax)currentReplacedNode;
if (originalExpression.Condition != previousOriginalNode)
{
ExpressionSyntax originalOtherPartOfConditional, newOtherPartOfConditional;
if (originalExpression.WhenTrue == previousOriginalNode)
{
Debug.Assert(newExpression.WhenTrue == previousReplacedNode);
originalOtherPartOfConditional = originalExpression.WhenFalse;
newOtherPartOfConditional = newExpression.WhenFalse;
}
else
{
Debug.Assert(newExpression.WhenFalse == previousReplacedNode);
originalOtherPartOfConditional = originalExpression.WhenTrue;
newOtherPartOfConditional = newExpression.WhenTrue;
}
var originalExpressionTypeInfo = this.OriginalSemanticModel.GetTypeInfo(originalExpression, this.CancellationToken);
var newExpressionTypeInfo = this.SpeculativeSemanticModel.GetTypeInfo(newExpression, this.CancellationToken);
var originalExpressionType = originalExpressionTypeInfo.Type;
var newExpressionType = newExpressionTypeInfo.Type;
// A conditional expression may have no type of it's own, but can be converted to a type if there's target-typed
// conditional expressions in play. For example:
//
// int? x = conditional ? (int?)trueValue : null;
//
// Once you remove the cast, the conditional has no type itself, but is being converted to int? by a conditional
// expression conversion.
if (newExpressionType == null &&
this.SpeculativeSemanticModel.GetConversion(newExpression, this.CancellationToken).IsConditionalExpression)
{
newExpressionType = newExpressionTypeInfo.ConvertedType;
}
if (originalExpressionType == null || newExpressionType == null)
{
// With the current implementation of the C# binder, this is impossible, but it's probably not wise to
// depend on an implementation detail of another layer.
return originalExpressionType != newExpressionType;
}
var originalConversion = this.OriginalSemanticModel.ClassifyConversion(originalOtherPartOfConditional, originalExpressionType);
var newConversion = this.SpeculativeSemanticModel.ClassifyConversion(newOtherPartOfConditional, newExpressionType);
// If this changes a boxing operation in one of the branches, we assume that semantics will change.
if (originalConversion.IsBoxing != newConversion.IsBoxing)
{
return true;
}
if (ReplacementBreaksBoxingInConditionalExpression(originalExpressionTypeInfo, newExpressionTypeInfo, (ExpressionSyntax)previousOriginalNode, (ExpressionSyntax)previousReplacedNode))
{
return true;
}
if (!ConversionsAreCompatible(originalConversion, newConversion))
{
return true;
}
}
}
else if (currentOriginalNode is CaseSwitchLabelSyntax originalCaseSwitchLabel)
{
var newCaseSwitchLabel = (CaseSwitchLabelSyntax)currentReplacedNode;
// If case label is changing, then need to check if the semantics will change for the switch expression.
// e.g. if switch expression is "switch(x)" where "object x = 1f", then "case 1:" and "case (float) 1:" are different.
var originalCaseType = this.OriginalSemanticModel.GetTypeInfo(previousOriginalNode, this.CancellationToken).Type;
var newCaseType = this.SpeculativeSemanticModel.GetTypeInfo(previousReplacedNode, this.CancellationToken).Type;
if (Equals(originalCaseType, newCaseType))
return false;
var oldSwitchStatement = (SwitchStatementSyntax)originalCaseSwitchLabel.Parent.Parent;
var newSwitchStatement = (SwitchStatementSyntax)newCaseSwitchLabel.Parent.Parent;
var originalConversion = this.OriginalSemanticModel.ClassifyConversion(oldSwitchStatement.Expression, originalCaseType);
var newConversion = this.SpeculativeSemanticModel.ClassifyConversion(newSwitchStatement.Expression, newCaseType);
// if conversion only exists for either original or new, then semantics changed.
if (originalConversion.Exists != newConversion.Exists)
{
return true;
}
// Same conversion cannot result in both originalCaseType and newCaseType, which means the semantics changed
// (since originalCaseType != newCaseType)
return originalConversion == newConversion;
}
else if (currentOriginalNode is SwitchStatementSyntax originalSwitchStatement &&
originalSwitchStatement.Expression == previousOriginalNode)
{
// Switch statement's expression changed, verify that the conversions from switch case labels to new switch
// expression type are not broken.
var newSwitchStatement = (SwitchStatementSyntax)currentReplacedNode;
var previousReplacedExpression = (ExpressionSyntax)previousReplacedNode;
// it is never legal to use `default/null` in a switch statement's expression.
if (previousReplacedExpression.WalkDownParentheses().Kind() is SyntaxKind.NullLiteralExpression or SyntaxKind.DefaultLiteralExpression)
return true;
var originalSwitchLabels = originalSwitchStatement.Sections.SelectMany(section => section.Labels).ToArray();
var newSwitchLabels = newSwitchStatement.Sections.SelectMany(section => section.Labels).ToArray();
for (var i = 0; i < originalSwitchLabels.Length; i++)
{
if (originalSwitchLabels[i] is CaseSwitchLabelSyntax originalSwitchLabel &&
newSwitchLabels[i] is CaseSwitchLabelSyntax newSwitchLabel &&
!ImplicitConversionsAreCompatible(originalSwitchLabel.Value, newSwitchLabel.Value))
{
return true;
}
}
}
else if (currentOriginalNode is SwitchExpressionSyntax originalSwitchExpression &&
originalSwitchExpression.GoverningExpression == previousOriginalNode)
{
var replacedSwitchExpression = (SwitchExpressionSyntax)currentReplacedNode;
// Switch expression's expression changed. Ensure it's the same type as before. If not, inference of
// the meaning of the patterns within can change.
var originalExprType = this.OriginalSemanticModel.GetTypeInfo(originalSwitchExpression.GoverningExpression, CancellationToken);
var replacedExprType = this.SpeculativeSemanticModel.GetTypeInfo(replacedSwitchExpression.GoverningExpression, CancellationToken);
if (!Equals(originalExprType.Type, replacedExprType.Type))
return true;
}
else if (currentOriginalNode is IfStatementSyntax originalIfStatement)
{
var newIfStatement = (IfStatementSyntax)currentReplacedNode;
if (originalIfStatement.Condition == previousOriginalNode)
{
// If condition changed, verify that original and replaced expression types are compatible.
if (!TypesAreCompatible(originalIfStatement.Condition, newIfStatement.Condition))
{
return true;
}
}
}
else if (currentOriginalNode is ConstructorInitializerSyntax originalCtorInitializer)
{
var newCtorInitializer = (ConstructorInitializerSyntax)currentReplacedNode;
return ReplacementBreaksConstructorInitializer(originalCtorInitializer, newCtorInitializer);
}
else if (currentOriginalNode.Kind() == SyntaxKind.CollectionInitializerExpression)
{
return previousOriginalNode != null &&
ReplacementBreaksCollectionInitializerAddMethod((ExpressionSyntax)previousOriginalNode, (ExpressionSyntax)previousReplacedNode);
}
else if (currentOriginalNode.Kind() == SyntaxKind.ImplicitArrayCreationExpression)
{
return !TypesAreCompatible((ExpressionSyntax)currentOriginalNode, (ExpressionSyntax)currentReplacedNode);
}
else if (currentOriginalNode is AnonymousObjectMemberDeclaratorSyntax originalAnonymousObjectMemberDeclarator)
{
var replacedAnonymousObjectMemberDeclarator = (AnonymousObjectMemberDeclaratorSyntax)currentReplacedNode;
return ReplacementBreaksAnonymousObjectMemberDeclarator(originalAnonymousObjectMemberDeclarator, replacedAnonymousObjectMemberDeclarator);
}
else if (currentOriginalNode.Kind() == SyntaxKind.DefaultExpression)
{
return !TypesAreCompatible((ExpressionSyntax)currentOriginalNode, (ExpressionSyntax)currentReplacedNode);
}
return false;
}
/// <summary>
/// Checks if the conversion might change the resultant boxed type.
/// Similar boxing checks are performed elsewhere, but in this case we need to perform the check on the entire conditional expression.
/// This will make sure the resultant cast is proper for the type of the conditional expression.
/// </summary>
private bool ReplacementBreaksBoxingInConditionalExpression(TypeInfo originalExpressionTypeInfo, TypeInfo newExpressionTypeInfo, ExpressionSyntax previousOriginalNode, ExpressionSyntax previousReplacedNode)
{
// If the resultant types are different and it is boxing to the converted type then semantics could be changing.
if (!Equals(originalExpressionTypeInfo.Type, newExpressionTypeInfo.Type))
{
var originalConvertedTypeConversion = this.OriginalSemanticModel.ClassifyConversion(previousOriginalNode, originalExpressionTypeInfo.ConvertedType);
var newExpressionConvertedTypeConversion = this.SpeculativeSemanticModel.ClassifyConversion(previousReplacedNode, newExpressionTypeInfo.ConvertedType);
if (originalConvertedTypeConversion.IsBoxing && newExpressionConvertedTypeConversion.IsBoxing)
{
return true;
}
}
return false;
}
private bool ReplacementBreaksAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax originalAnonymousObjectMemberDeclarator, AnonymousObjectMemberDeclaratorSyntax replacedAnonymousObjectMemberDeclarator)
{
var originalExpressionType = this.OriginalSemanticModel.GetTypeInfo(originalAnonymousObjectMemberDeclarator.Expression, this.CancellationToken).Type;
var newExpressionType = this.SpeculativeSemanticModel.GetTypeInfo(replacedAnonymousObjectMemberDeclarator.Expression, this.CancellationToken).Type;
return !object.Equals(originalExpressionType, newExpressionType);
}
private bool ReplacementBreaksConstructorInitializer(ConstructorInitializerSyntax ctorInitializer, ConstructorInitializerSyntax newCtorInitializer)
{
var originalSymbol = this.OriginalSemanticModel.GetSymbolInfo(ctorInitializer, CancellationToken).Symbol;
var newSymbol = this.SpeculativeSemanticModel.GetSymbolInfo(newCtorInitializer, CancellationToken).Symbol;
return !SymbolsAreCompatible(originalSymbol, newSymbol);
}
private bool ReplacementBreaksCollectionInitializerAddMethod(ExpressionSyntax originalInitializer, ExpressionSyntax newInitializer)
{
var originalSymbol = this.OriginalSemanticModel.GetCollectionInitializerSymbolInfo(originalInitializer, CancellationToken).Symbol;
var newSymbol = this.SpeculativeSemanticModel.GetCollectionInitializerSymbolInfo(newInitializer, CancellationToken).Symbol;
return !SymbolsAreCompatible(originalSymbol, newSymbol);
}
protected override bool ExpressionMightReferenceMember(SyntaxNode node)
=> node.Kind() is
SyntaxKind.InvocationExpression or
SyntaxKind.ElementAccessExpression or
SyntaxKind.SimpleMemberAccessExpression or
SyntaxKind.ImplicitElementAccess or
SyntaxKind.ObjectCreationExpression;
protected override ImmutableArray<ArgumentSyntax> GetArguments(ExpressionSyntax expression)
{
var argumentsList = GetArgumentList(expression);
return argumentsList != null
? argumentsList.Arguments.AsImmutableOrEmpty()
: default;
}
private static BaseArgumentListSyntax GetArgumentList(ExpressionSyntax expression)
{
expression = expression.WalkDownParentheses();
return expression.Kind() switch
{
SyntaxKind.InvocationExpression => ((InvocationExpressionSyntax)expression).ArgumentList,
SyntaxKind.ObjectCreationExpression => ((ObjectCreationExpressionSyntax)expression).ArgumentList,
SyntaxKind.ElementAccessExpression => ((ElementAccessExpressionSyntax)expression).ArgumentList,
_ => null,
};
}
protected override ExpressionSyntax GetReceiver(ExpressionSyntax expression)
{
expression = expression.WalkDownParentheses();
switch (expression.Kind())
{
case SyntaxKind.SimpleMemberAccessExpression:
return ((MemberAccessExpressionSyntax)expression).Expression;
case SyntaxKind.InvocationExpression:
{
var result = ((InvocationExpressionSyntax)expression).Expression;
if (result.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
return GetReceiver(result);
}
return result;
}
case SyntaxKind.ElementAccessExpression:
{
var result = ((ElementAccessExpressionSyntax)expression).Expression;
if (result.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
result = GetReceiver(result);
}
return result;
}
default:
return null;
}
}
protected override bool IsInNamespaceOrTypeContext(ExpressionSyntax node)
=> SyntaxFacts.IsInNamespaceOrTypeContext(node);
protected override ExpressionSyntax GetForEachStatementExpression(CommonForEachStatementSyntax forEachStatement)
=> forEachStatement.Expression;
protected override ExpressionSyntax GetThrowStatementExpression(ThrowStatementSyntax throwStatement)
=> throwStatement.Expression;
protected override bool IsForEachTypeInferred(CommonForEachStatementSyntax forEachStatement, SemanticModel semanticModel)
=> forEachStatement switch
{
ForEachStatementSyntax foreachStatement => foreachStatement.Type.IsTypeInferred(semanticModel),
ForEachVariableStatementSyntax { Variable: DeclarationExpressionSyntax declarationExpression } => declarationExpression.Type.IsTypeInferred(semanticModel),
_ => false,
};
protected override bool IsParenthesizedExpression(SyntaxNode node)
=> node.IsKind(SyntaxKind.ParenthesizedExpression);
protected override bool IsNamedArgument(ArgumentSyntax argument)
=> argument.NameColon != null && !argument.NameColon.IsMissing;
protected override string GetNamedArgumentIdentifierValueText(ArgumentSyntax argument)
=> argument.NameColon.Name.Identifier.ValueText;
private bool ReplacementBreaksBinaryExpression(BinaryExpressionSyntax binaryExpression, BinaryExpressionSyntax newBinaryExpression)
{
if (binaryExpression.Kind() is SyntaxKind.AsExpression or SyntaxKind.IsExpression &&
ReplacementBreaksIsOrAsExpression(binaryExpression, newBinaryExpression))
{
return true;
}
return !SymbolsAreCompatible(binaryExpression, newBinaryExpression) ||
!TypesAreCompatible(binaryExpression, newBinaryExpression) ||
!ImplicitConversionsAreCompatible(binaryExpression, newBinaryExpression);
}
private bool ReplacementBreaksConditionalAccessExpression(ConditionalAccessExpressionSyntax conditionalAccessExpression, ConditionalAccessExpressionSyntax newConditionalAccessExpression)
{
return !SymbolsAreCompatible(conditionalAccessExpression, newConditionalAccessExpression) ||
!TypesAreCompatible(conditionalAccessExpression, newConditionalAccessExpression) ||
!SymbolsAreCompatible(conditionalAccessExpression.WhenNotNull, newConditionalAccessExpression.WhenNotNull) ||
!TypesAreCompatible(conditionalAccessExpression.WhenNotNull, newConditionalAccessExpression.WhenNotNull);
}
private bool ReplacementBreaksIsOrAsExpression(BinaryExpressionSyntax originalIsOrAsExpression, BinaryExpressionSyntax newIsOrAsExpression)
{
// Special case: Lambda expressions and anonymous delegates cannot appear
// on the left-side of an 'is' or 'as' cast. We can handle this case syntactically.
if (originalIsOrAsExpression.Left.WalkDownParentheses() is not AnonymousFunctionExpressionSyntax &&
newIsOrAsExpression.Left.WalkDownParentheses() is AnonymousFunctionExpressionSyntax)
{
return true;
}
var originalConvertedType = this.OriginalSemanticModel.GetTypeInfo(originalIsOrAsExpression.Right).Type;
var newConvertedType = this.SpeculativeSemanticModel.GetTypeInfo(newIsOrAsExpression.Right).Type;
if (originalConvertedType == null || newConvertedType == null)
{
return originalConvertedType != newConvertedType;
}
var originalConversion = this.OriginalSemanticModel.ClassifyConversion(originalIsOrAsExpression.Left, originalConvertedType, isExplicitInSource: true);
var newConversion = this.SpeculativeSemanticModel.ClassifyConversion(newIsOrAsExpression.Left, newConvertedType, isExplicitInSource: true);
// Is and As operators do not consider any user-defined operators, just ensure that the conversion exists.
return originalConversion.Exists != newConversion.Exists;
}
private bool ReplacementBreaksAssignmentExpression(AssignmentExpressionSyntax assignmentExpression, AssignmentExpressionSyntax newAssignmentExpression)
{
if (assignmentExpression.IsCompoundAssignExpression() &&
assignmentExpression.Kind() != SyntaxKind.LeftShiftAssignmentExpression &&
assignmentExpression.Kind() != SyntaxKind.RightShiftAssignmentExpression &&
ReplacementBreaksCompoundAssignment(assignmentExpression.Left, assignmentExpression.Right, newAssignmentExpression.Left, newAssignmentExpression.Right))
{
return true;
}
return !SymbolsAreCompatible(assignmentExpression, newAssignmentExpression) ||
!TypesAreCompatible(assignmentExpression, newAssignmentExpression) ||
!ImplicitConversionsAreCompatible(assignmentExpression, newAssignmentExpression);
}
private bool ReplacementBreaksQueryClause(QueryClauseSyntax originalClause, QueryClauseSyntax newClause)
{
// Ensure QueryClauseInfos are compatible.
var originalClauseInfo = this.OriginalSemanticModel.GetQueryClauseInfo(originalClause, this.CancellationToken);
var newClauseInfo = this.SpeculativeSemanticModel.GetQueryClauseInfo(newClause, this.CancellationToken);
return !SymbolInfosAreCompatible(originalClauseInfo.CastInfo, newClauseInfo.CastInfo) ||
!SymbolInfosAreCompatible(originalClauseInfo.OperationInfo, newClauseInfo.OperationInfo);
}
protected override bool ReplacementIntroducesDisallowedNullType(
ExpressionSyntax originalExpression,
ExpressionSyntax newExpression,
TypeInfo originalTypeInfo,
TypeInfo newTypeInfo)
{
// If the base check is fine with the nullability of types before/after, then we're good and there are no
// more checks we need to do.
if (!base.ReplacementIntroducesDisallowedNullType(originalExpression, newExpression, originalTypeInfo, newTypeInfo))
return false;
// If, however, the base check is not ok. That means that the old expression had an initial type, but the
// new expression does not. This may or may not be ok depending on the construct. If it's a supported
// construct then we want to check the new constructs converted type against the old construct's original
// type to make sure those still match. If so, this change is fine.
if (IsSupportedConstructWithNullType() &&
SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType))
{
return false;
}
return true;
bool IsSupportedConstructWithNullType()
{
// A conditional expression may become untyped if it now involves a conditional conversion. For example:
//
// int? s = x ? 0 : null;
//
// In this case, the null type is allowed if we do have a conditional-expression-conversion *and* the
// converted type matches the original type.
if (newExpression.IsKind(SyntaxKind.ConditionalExpression) &&
ConditionalExpressionConversionsAreAllowed(newExpression) &&
this.SpeculativeSemanticModel.GetConversion(newExpression).IsConditionalExpression)
{
return true;
}
// Similar to above, it's fine for a switch expression to potentially change to having a 'null' direct type
// (as long as a target-typed switch-expression conversion happened). Note: unlike above, we don't have to
// check a language version since switch expressions always supported target-typed conversion.
if (newExpression.IsKind(SyntaxKind.SwitchExpression) &&
this.SpeculativeSemanticModel.GetConversion(newExpression).IsSwitchExpression &&
SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType))
{
return true;
}
// Similar to above, it's fine for a collection expression to have a 'null' direct type (as long as a
// target-typed collection-expression conversion happened). Note: unlike above, we don't have to check
// a language version since collection expressions always supported collection-expression-conversions.
if (newExpression.IsKind(SyntaxKind.CollectionExpression) &&
this.SpeculativeSemanticModel.GetConversion(newExpression).IsCollectionExpression)
{
return true;
}
// Similar to above, it's fine for a tuple expression to have a 'null' direct type (as long as a
// target-typed tuple-expression conversion happened). Note: unlike above, we don't have to check
// a language version since tuple expressions always supported tuple-expression-conversions.
if (newExpression.IsKind(SyntaxKind.TupleExpression) &&
this.SpeculativeSemanticModel.GetConversion(newExpression).IsTupleLiteralConversion &&
SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType))
{
return true;
}
return false;
}
}
protected override bool ConversionsAreCompatible(SemanticModel originalModel, ExpressionSyntax originalExpression, SemanticModel newModel, ExpressionSyntax newExpression)
{
var originalConversion = originalModel.GetConversion(originalExpression);
var newConversion = newModel.GetConversion(newExpression);
if (originalExpression.IsKind(SyntaxKind.ConditionalExpression) &&
newExpression.IsKind(SyntaxKind.ConditionalExpression))
{
if (newConversion.IsConditionalExpression)
{
// If we went from a non-conditional-conversion to a conditional-conversion (i.e. by removing a
// cast), then that is always an error before CSharp9, and should not be allowed.
if (!originalConversion.IsConditionalExpression && !ConditionalExpressionConversionsAreAllowed(originalExpression))
return false;
// If the only change to the conversion here is the introduction of a conditional expression conversion,
// that means types didn't really change in a meaningful way.
if (originalConversion.IsIdentity)
return true;
}
}
return ConversionsAreCompatible(originalConversion, newConversion);
}
private static bool ConditionalExpressionConversionsAreAllowed(ExpressionSyntax originalExpression)
=> originalExpression.GetLanguageVersion() >= LanguageVersion.CSharp9;
protected override bool ConversionsAreCompatible(ExpressionSyntax originalExpression, ITypeSymbol originalTargetType, ExpressionSyntax newExpression, ITypeSymbol newTargetType)
{
this.GetConversions(originalExpression, originalTargetType, newExpression, newTargetType, out var originalConversion, out var newConversion);
if (originalConversion == null || newConversion == null)
{
return false;
}
return ConversionsAreCompatible(originalConversion.Value, newConversion.Value);
}
private bool ConversionsAreCompatible(Conversion originalConversion, Conversion newConversion)
{
if (originalConversion.Exists != newConversion.Exists ||
(!originalConversion.IsExplicit && newConversion.IsExplicit))
{
return false;
}
var originalIsUserDefined = originalConversion.IsUserDefined;
var newIsUserDefined = newConversion.IsUserDefined;
if (originalIsUserDefined != newIsUserDefined)
{
return false;
}
if (originalIsUserDefined || originalConversion.MethodSymbol != null || newConversion.MethodSymbol != null)
{
return SymbolsAreCompatible(originalConversion.MethodSymbol, newConversion.MethodSymbol);
}
return true;
}
protected override bool ForEachConversionsAreCompatible(SemanticModel originalModel, CommonForEachStatementSyntax originalForEach, SemanticModel newModel, CommonForEachStatementSyntax newForEach)
{
var originalInfo = originalModel.GetForEachStatementInfo(originalForEach);
var newInfo = newModel.GetForEachStatementInfo(newForEach);
return ConversionsAreCompatible(originalInfo.CurrentConversion, newInfo.CurrentConversion)
&& ConversionsAreCompatible(originalInfo.ElementConversion, newInfo.ElementConversion);
}
protected override void GetForEachSymbols(
SemanticModel model,
CommonForEachStatementSyntax forEach,
out IMethodSymbol getEnumeratorMethod,
out ITypeSymbol elementType,
out ImmutableArray<ILocalSymbol> localVariables)
{
var info = model.GetForEachStatementInfo(forEach);
getEnumeratorMethod = info.GetEnumeratorMethod;
elementType = info.ElementType;
if (forEach is ForEachStatementSyntax foreachStatement)
{
localVariables = [(ILocalSymbol)model.GetRequiredDeclaredSymbol(foreachStatement, this.CancellationToken)];
}
else if (forEach is ForEachVariableStatementSyntax { Variable: DeclarationExpressionSyntax declarationExpression })
{
using var variables = TemporaryArray<ILocalSymbol>.Empty;
AddVariables(declarationExpression.Designation, ref variables.AsRef());
localVariables = variables.ToImmutableAndClear();
}
else
{
localVariables = [];
}
return;
void AddVariables(VariableDesignationSyntax designation, ref TemporaryArray<ILocalSymbol> variables)
{
switch (designation)
{
case SingleVariableDesignationSyntax singleVariableDesignation:
variables.Add((ILocalSymbol)model.GetRequiredDeclaredSymbol(singleVariableDesignation, CancellationToken));
break;
case ParenthesizedVariableDesignationSyntax parenthesizedVariableDesignation:
foreach (var child in parenthesizedVariableDesignation.Variables)
AddVariables(child, ref variables);
break;
}
}
}
protected override bool IsReferenceConversion(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType)
=> compilation.ClassifyConversion(sourceType, targetType).IsReference;
protected override Conversion ClassifyConversion(SemanticModel model, ExpressionSyntax expression, ITypeSymbol targetType)
=> model.ClassifyConversion(expression, targetType);
protected override Conversion ClassifyConversion(SemanticModel model, ITypeSymbol originalType, ITypeSymbol targetType)
=> model.Compilation.ClassifyConversion(originalType, targetType);
}
|