|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.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 abstract class AbstractSpeculationAnalyzer<
TExpressionSyntax,
TTypeSyntax,
TAttributeSyntax,
TArgumentSyntax,
TForEachStatementSyntax,
TThrowStatementSyntax,
TInvocationExpressionSyntax,
TConversion> : ISpeculationAnalyzer
where TExpressionSyntax : SyntaxNode
where TTypeSyntax : TExpressionSyntax
where TAttributeSyntax : SyntaxNode
where TArgumentSyntax : SyntaxNode
where TForEachStatementSyntax : SyntaxNode
where TThrowStatementSyntax : SyntaxNode
where TInvocationExpressionSyntax : TExpressionSyntax
where TConversion : struct
{
private readonly TExpressionSyntax _newExpressionForReplace;
private readonly bool _skipVerificationForReplacedNode;
private readonly bool _failOnOverloadResolutionFailuresInOriginalCode;
private readonly bool _isNewSemanticModelSpeculativeModel;
private SyntaxNode? _lazySemanticRootOfOriginalExpression;
private TExpressionSyntax? _lazyReplacedExpression;
private SyntaxNode? _lazySemanticRootOfReplacedExpression;
private SemanticModel? _lazySpeculativeSemanticModel;
private static readonly SymbolEquivalenceComparer s_includeNullabilityComparer =
SymbolEquivalenceComparer.Create(distinguishRefFromOut: true, tupleNamesMustMatch: true, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: false, arrayAndReadOnlySpanCompareEqually: false);
private static readonly SymbolEquivalenceComparer s_arrayAndReadOnlySpanCompareEqually = s_includeNullabilityComparer.With(arrayAndReadOnlySpanCompareEqually: true);
/// <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 AbstractSpeculationAnalyzer(
TExpressionSyntax expression,
TExpressionSyntax newExpression,
SemanticModel semanticModel,
CancellationToken cancellationToken,
bool skipVerificationForReplacedNode = false,
bool failOnOverloadResolutionFailuresInOriginalCode = false)
{
OriginalExpression = expression;
_newExpressionForReplace = newExpression;
OriginalSemanticModel = semanticModel;
CancellationToken = cancellationToken;
_skipVerificationForReplacedNode = skipVerificationForReplacedNode;
_failOnOverloadResolutionFailuresInOriginalCode = failOnOverloadResolutionFailuresInOriginalCode;
_isNewSemanticModelSpeculativeModel = true;
_lazyReplacedExpression = null;
_lazySemanticRootOfOriginalExpression = null;
_lazySemanticRootOfReplacedExpression = null;
_lazySpeculativeSemanticModel = null;
}
protected abstract ISyntaxFacts SyntaxFactsService { get; }
protected abstract SyntaxNode GetSemanticRootForSpeculation(TExpressionSyntax expression);
protected abstract SemanticModel CreateSpeculativeSemanticModel(SyntaxNode originalNode, SyntaxNode nodeToSpeculate, SemanticModel semanticModel);
protected abstract bool IsInNamespaceOrTypeContext(TExpressionSyntax node);
protected abstract bool ExpressionMightReferenceMember([NotNullWhen(true)] SyntaxNode? node);
protected abstract bool CanAccessInstanceMemberThrough(TExpressionSyntax? expression);
protected abstract TConversion ClassifyConversion(SemanticModel model, TExpressionSyntax expression, ITypeSymbol targetType);
protected abstract TConversion ClassifyConversion(SemanticModel model, ITypeSymbol originalType, ITypeSymbol targetType);
protected abstract bool ConversionsAreCompatible(SemanticModel model1, TExpressionSyntax expression1, SemanticModel model2, TExpressionSyntax expression2);
protected abstract bool ConversionsAreCompatible(TExpressionSyntax originalExpression, ITypeSymbol originalTargetType, TExpressionSyntax newExpression, ITypeSymbol newTargetType);
protected abstract bool IsReferenceConversion(Compilation model, ITypeSymbol sourceType, ITypeSymbol targetType);
protected abstract TExpressionSyntax GetForEachStatementExpression(TForEachStatementSyntax forEachStatement);
protected abstract bool IsForEachTypeInferred(TForEachStatementSyntax forEachStatement, SemanticModel semanticModel);
protected abstract bool ForEachConversionsAreCompatible(SemanticModel originalModel, TForEachStatementSyntax originalForEach, SemanticModel newModel, TForEachStatementSyntax newForEach);
protected abstract void GetForEachSymbols(
SemanticModel model, TForEachStatementSyntax forEach, out IMethodSymbol getEnumeratorMethod, out ITypeSymbol elementType, out ImmutableArray<ILocalSymbol> localVariables);
protected abstract bool ReplacementChangesSemanticsForNodeLanguageSpecific(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode, SyntaxNode? previousOriginalNode, SyntaxNode? previousReplacedNode);
protected abstract bool IsParenthesizedExpression([NotNullWhen(true)] SyntaxNode? node);
protected abstract bool IsNamedArgument(TArgumentSyntax argument);
protected abstract string GetNamedArgumentIdentifierValueText(TArgumentSyntax argument);
protected abstract TExpressionSyntax GetThrowStatementExpression(TThrowStatementSyntax throwStatement);
protected abstract ImmutableArray<TArgumentSyntax> GetArguments(TExpressionSyntax expression);
protected abstract TExpressionSyntax GetReceiver(TExpressionSyntax expression);
/// <summary>
/// Original expression to be replaced.
/// </summary>
public TExpressionSyntax OriginalExpression { get; }
SyntaxNode ISpeculationAnalyzer.OriginalExpression => OriginalExpression;
/// <summary>
/// First ancestor of <see cref="OriginalExpression"/> which is either a statement, attribute, constructor initializer,
/// field initializer, default parameter initializer or type syntax node.
/// It serves as the root node for all semantic analysis for this syntax replacement.
/// </summary>
public SyntaxNode SemanticRootOfOriginalExpression
{
get
{
if (_lazySemanticRootOfOriginalExpression == null)
{
_lazySemanticRootOfOriginalExpression = GetSemanticRootForSpeculation(this.OriginalExpression);
RoslynDebug.AssertNotNull(_lazySemanticRootOfOriginalExpression);
}
return _lazySemanticRootOfOriginalExpression;
}
}
/// <summary>
/// Semantic model for the syntax tree corresponding to <see cref="OriginalExpression"/>
/// </summary>
public SemanticModel OriginalSemanticModel { get; }
/// <summary>
/// Node which replaces the <see cref="OriginalExpression"/>.
/// Note that this node is a cloned version of <see cref="_newExpressionForReplace"/> node, which has been re-parented
/// under the node to be speculated, i.e. <see cref="SemanticRootOfReplacedExpression"/>.
/// </summary>
public TExpressionSyntax ReplacedExpression
{
get
{
EnsureReplacedExpressionAndSemanticRoot();
return _lazyReplacedExpression;
}
}
SyntaxNode ISpeculationAnalyzer.ReplacedExpression => ReplacedExpression;
/// <summary>
/// Node created by replacing <see cref="OriginalExpression"/> under <see cref="SemanticRootOfOriginalExpression"/> node.
/// This node is used as the argument to the GetSpeculativeSemanticModel API and serves as the root node for all
/// semantic analysis of the speculated tree.
/// </summary>
public SyntaxNode SemanticRootOfReplacedExpression
{
get
{
EnsureReplacedExpressionAndSemanticRoot();
return _lazySemanticRootOfReplacedExpression;
}
}
/// <summary>
/// Speculative semantic model used for analyzing the semantics of the new tree.
/// </summary>
public SemanticModel SpeculativeSemanticModel
{
get
{
EnsureSpeculativeSemanticModel();
return _lazySpeculativeSemanticModel;
}
}
public CancellationToken CancellationToken { get; }
protected virtual SyntaxNode GetSemanticRootOfReplacedExpression(SyntaxNode semanticRootOfOriginalExpression, TExpressionSyntax annotatedReplacedExpression)
=> semanticRootOfOriginalExpression.ReplaceNode(this.OriginalExpression, annotatedReplacedExpression);
[MemberNotNull(nameof(_lazySemanticRootOfReplacedExpression), nameof(_lazyReplacedExpression))]
private void EnsureReplacedExpressionAndSemanticRoot()
{
if (_lazySemanticRootOfReplacedExpression == null)
{
// Because the new expression will change identity once we replace the old
// expression in its parent, we annotate it here to allow us to get back to
// it after replace.
var annotation = new SyntaxAnnotation();
var annotatedExpression = _newExpressionForReplace.WithAdditionalAnnotations(annotation);
_lazySemanticRootOfReplacedExpression = GetSemanticRootOfReplacedExpression(this.SemanticRootOfOriginalExpression, annotatedExpression);
_lazyReplacedExpression = (TExpressionSyntax)_lazySemanticRootOfReplacedExpression.GetAnnotatedNodesAndTokens(annotation).Single().AsNode()!;
}
else
{
RoslynDebug.AssertNotNull(_lazyReplacedExpression);
}
}
[Conditional("DEBUG")]
protected abstract void ValidateSpeculativeSemanticModel(SemanticModel speculativeSemanticModel, SyntaxNode nodeToSpeculate);
[MemberNotNull(nameof(_lazySpeculativeSemanticModel))]
private void EnsureSpeculativeSemanticModel()
{
if (_lazySpeculativeSemanticModel == null)
{
var nodeToSpeculate = this.SemanticRootOfReplacedExpression;
_lazySpeculativeSemanticModel = CreateSpeculativeSemanticModel(this.SemanticRootOfOriginalExpression, nodeToSpeculate, OriginalSemanticModel);
ValidateSpeculativeSemanticModel(_lazySpeculativeSemanticModel, nodeToSpeculate);
}
}
#region Semantic comparison helpers
protected virtual bool ReplacementIntroducesDisallowedNullType(
TExpressionSyntax originalExpression,
TExpressionSyntax newExpression,
TypeInfo originalTypeInfo,
TypeInfo newTypeInfo)
{
// If the original expression had no type, it's fine for the new one to have no type either.
if (originalTypeInfo.Type == null)
return false;
// If the original had a type, but the new expression doesn't, this is *normally* bad. However, there are
// some cases where it is ok (untyped language expressions that have natural conversions to typed
// expressions). Subclasses can override this for those cases.
if (newTypeInfo.Type == null)
return true;
// Otherwise, things look ok so far.
return false;
}
protected bool TypesAreCompatible(TExpressionSyntax originalExpression, TExpressionSyntax newExpression)
{
RoslynDebug.AssertNotNull(originalExpression);
Debug.Assert(this.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalExpression));
RoslynDebug.AssertNotNull(newExpression);
Debug.Assert(this.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newExpression));
var originalTypeInfo = this.OriginalSemanticModel.GetTypeInfo(originalExpression);
var newTypeInfo = this.SpeculativeSemanticModel.GetTypeInfo(newExpression);
if (SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.Type))
return true;
// types changed between the old and new expression (specifically, the new type became null, while the
// original type was not). That's ok in some circumstance. Check for those and allow in that specific
// case.
if (originalTypeInfo.Type != null && newTypeInfo.Type == null &&
!ReplacementIntroducesDisallowedNullType(originalExpression, newExpression, originalTypeInfo, newTypeInfo))
{
return true;
}
return false;
}
protected bool ConvertedTypesAreCompatible(TExpressionSyntax originalExpression, TExpressionSyntax newExpression)
{
RoslynDebug.AssertNotNull(originalExpression);
Debug.Assert(this.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalExpression));
RoslynDebug.AssertNotNull(newExpression);
Debug.Assert(this.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newExpression));
var originalTypeInfo = this.OriginalSemanticModel.GetTypeInfo(originalExpression);
var newTypeInfo = this.SpeculativeSemanticModel.GetTypeInfo(newExpression);
return SymbolsAreCompatible(originalTypeInfo.ConvertedType, newTypeInfo.ConvertedType);
}
protected bool ImplicitConversionsAreCompatible(TExpressionSyntax originalExpression, TExpressionSyntax newExpression)
{
RoslynDebug.AssertNotNull(originalExpression);
Debug.Assert(this.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalExpression));
RoslynDebug.AssertNotNull(newExpression);
Debug.Assert(this.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newExpression));
return ConversionsAreCompatible(this.OriginalSemanticModel, originalExpression, this.SpeculativeSemanticModel, newExpression);
}
private bool ImplicitConversionsAreCompatible(TExpressionSyntax originalExpression, ITypeSymbol originalTargetType, TExpressionSyntax newExpression, ITypeSymbol newTargetType)
{
RoslynDebug.AssertNotNull(originalExpression);
Debug.Assert(this.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalExpression));
RoslynDebug.AssertNotNull(newExpression);
Debug.Assert(this.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newExpression));
RoslynDebug.AssertNotNull(originalTargetType);
RoslynDebug.AssertNotNull(newTargetType);
return ConversionsAreCompatible(originalExpression, originalTargetType, newExpression, newTargetType);
}
protected bool SymbolsAreCompatible(SyntaxNode originalNode, SyntaxNode newNode, bool requireNonNullSymbols = false)
{
RoslynDebug.AssertNotNull(originalNode);
Debug.Assert(this.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalNode));
RoslynDebug.AssertNotNull(newNode);
Debug.Assert(this.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newNode));
var originalSymbolInfo = this.OriginalSemanticModel.GetSymbolInfo(originalNode);
var newSymbolInfo = this.SpeculativeSemanticModel.GetSymbolInfo(newNode);
return SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo, requireNonNullSymbols);
}
public static bool SymbolInfosAreCompatible(SymbolInfo originalSymbolInfo, SymbolInfo newSymbolInfo, bool performEquivalenceCheck, bool requireNonNullSymbols = false)
{
if (originalSymbolInfo.CandidateReason == newSymbolInfo.CandidateReason)
{
if (SymbolsAreCompatibleCore(originalSymbolInfo.Symbol, newSymbolInfo.Symbol, performEquivalenceCheck, requireNonNullSymbols))
return true;
if (originalSymbolInfo.CandidateReason == CandidateReason.MemberGroup)
{
var candidateLength = originalSymbolInfo.CandidateSymbols.Length;
if (candidateLength > 0 && candidateLength == newSymbolInfo.CandidateSymbols.Length)
{
for (int i = 0, n = candidateLength; i < n; i++)
{
if (!SymbolsAreCompatibleCore(originalSymbolInfo.CandidateSymbols[i], newSymbolInfo.CandidateSymbols[i], performEquivalenceCheck, requireNonNullSymbols))
return false;
}
return true;
}
}
}
return false;
}
protected bool SymbolInfosAreCompatible(SymbolInfo originalSymbolInfo, SymbolInfo newSymbolInfo, bool requireNonNullSymbols = false)
=> SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo, performEquivalenceCheck: !_isNewSemanticModelSpeculativeModel, requireNonNullSymbols: requireNonNullSymbols);
protected bool SymbolsAreCompatible(ISymbol? symbol, ISymbol? newSymbol, bool requireNonNullSymbols = false)
=> SymbolsAreCompatibleCore(symbol, newSymbol, performEquivalenceCheck: !_isNewSemanticModelSpeculativeModel, requireNonNullSymbols: requireNonNullSymbols);
private static bool SymbolsAreCompatibleCore(
ISymbol? symbol,
ISymbol? newSymbol,
bool performEquivalenceCheck,
bool requireNonNullSymbols = false)
{
if (symbol == null && newSymbol == null)
{
return !requireNonNullSymbols;
}
if (symbol == null || newSymbol == null)
{
return false;
}
if (symbol.IsReducedExtension())
{
symbol = ((IMethodSymbol)symbol).GetConstructedReducedFrom()!;
}
if (newSymbol.IsReducedExtension())
{
newSymbol = ((IMethodSymbol)newSymbol).GetConstructedReducedFrom()!;
}
// TODO: Lambda function comparison performs syntax equality, hence is non-trivial to compare lambda methods across different compilations.
// For now, just assume they are equal.
if (symbol.IsAnonymousFunction())
{
return newSymbol.IsAnonymousFunction();
}
if (performEquivalenceCheck)
{
// We are comparing symbols across two semantic models (where neither is the speculative model of other one).
// We will use the SymbolEquivalenceComparer to check if symbols are equivalent.
return CompareAcrossSemanticModels(symbol, newSymbol);
}
if (s_includeNullabilityComparer.Equals(symbol, newSymbol))
return true;
if (symbol is IMethodSymbol methodSymbol && newSymbol is IMethodSymbol newMethodSymbol)
{
// If we have local functions, we can't use normal symbol equality for them (since that checks locations).
// Have to defer to SymbolEquivalence instead.
if (methodSymbol.MethodKind == MethodKind.LocalFunction && newMethodSymbol.MethodKind == MethodKind.LocalFunction)
return CompareAcrossSemanticModels(methodSymbol, newMethodSymbol);
// Handle equivalence of special built-in comparison operators between enum types and
// it's underlying enum type.
if (methodSymbol.TryGetPredefinedComparisonOperator(out var originalOp) &&
newMethodSymbol.TryGetPredefinedComparisonOperator(out var newOp) &&
originalOp == newOp)
{
var type = methodSymbol.ContainingType;
var newType = newMethodSymbol.ContainingType;
if (type != null && newType != null)
{
if (EnumTypesAreCompatible(type, newType) ||
EnumTypesAreCompatible(newType, type))
{
return true;
}
}
}
// We consider two method overloads compatible if one takes a T[] array for a particular parameter, and the
// other takes a ReadOnlySpan<T> for the same parameter. This is a considered a supported and desirable API
// upgrade story for API authors. Specifically, they start with an array-based method, and then add a
// sibling ROS method. In that case, the language will prefer the latter when both are applicable. So if
// we make a code change that makes the second compatible, then we are ok with that, as the expectation is
// that the new method has the same semantics and it is desirable for code to now call that.
//
// Note: this comparer will check the method kinds, name, containing type, arity, and virtually everything
// else about the methods. The only difference it will allow between the methods is that parameter types
// can be different if they are an array vs a ReadOnlySpan.
if (s_arrayAndReadOnlySpanCompareEqually.Equals(methodSymbol, newMethodSymbol))
return true;
}
return false;
}
private static bool CompareAcrossSemanticModels(ISymbol symbol, ISymbol newSymbol)
{
// SymbolEquivalenceComparer performs Location equality checks for locals, labels, range-variables and local
// functions. As we are comparing symbols from different semantic models, locations will differ. Hence
// perform minimal checks for these symbol kinds.
if (symbol.Kind != newSymbol.Kind)
return false;
if (symbol is ILocalSymbol localSymbol && newSymbol is ILocalSymbol newLocalSymbol)
{
return newSymbol.IsImplicitlyDeclared == symbol.IsImplicitlyDeclared &&
symbol.Name == newSymbol.Name &&
CompareAcrossSemanticModels(localSymbol.Type, newLocalSymbol.Type);
}
if (symbol is ILabelSymbol && newSymbol is ILabelSymbol)
return symbol.Name == newSymbol.Name;
if (symbol is IRangeVariableSymbol && newSymbol is IRangeVariableSymbol)
return symbol.Name == newSymbol.Name;
if (symbol is IParameterSymbol parameterSymbol &&
newSymbol is IParameterSymbol newParameterSymbol &&
parameterSymbol.ContainingSymbol.IsAnonymousOrLocalFunction() &&
newParameterSymbol.ContainingSymbol.IsAnonymousOrLocalFunction())
{
return symbol.Name == newSymbol.Name &&
parameterSymbol.IsRefOrOut() == newParameterSymbol.IsRefOrOut() &&
CompareAcrossSemanticModels(parameterSymbol.Type, newParameterSymbol.Type);
}
if (symbol is IMethodSymbol { MethodKind: MethodKind.LocalFunction } methodSymbol &&
newSymbol is IMethodSymbol { MethodKind: MethodKind.LocalFunction } newMethodSymbol)
{
return symbol.Name == newSymbol.Name &&
methodSymbol.Parameters.Length == newMethodSymbol.Parameters.Length &&
CompareAcrossSemanticModels(methodSymbol.ReturnType, newMethodSymbol.ReturnType) &&
methodSymbol.Parameters.Zip(newMethodSymbol.Parameters, (p1, p2) => (p1, p2)).All(
t => CompareAcrossSemanticModels(t.p1, t.p2));
}
return SymbolEquivalenceComparer.Instance.Equals(symbol, newSymbol);
}
private static bool EnumTypesAreCompatible(INamedTypeSymbol type1, INamedTypeSymbol type2)
=> type1.IsEnumType() &&
type1.EnumUnderlyingType?.SpecialType == type2.SpecialType;
#endregion
/// <summary>
/// Determines whether performing the given syntax replacement will change the semantics of any parenting expressions
/// by performing a bottom up walk from the <see cref="OriginalExpression"/> up to <see cref="SemanticRootOfOriginalExpression"/>
/// in the original tree and simultaneously walking bottom up from <see cref="ReplacedExpression"/> up to <see cref="SemanticRootOfReplacedExpression"/>
/// in the speculated syntax tree and performing appropriate semantic comparisons.
/// </summary>
public bool ReplacementChangesSemantics()
{
if (this.SemanticRootOfOriginalExpression is TTypeSyntax)
{
var originalType = (TTypeSyntax)this.OriginalExpression;
var newType = (TTypeSyntax)this.ReplacedExpression;
return ReplacementBreaksTypeResolution(originalType, newType, useSpeculativeModel: false);
}
return ReplacementChangesSemantics(
currentOriginalNode: this.OriginalExpression,
currentReplacedNode: this.ReplacedExpression,
originalRoot: this.SemanticRootOfOriginalExpression,
skipVerificationForCurrentNode: _skipVerificationForReplacedNode);
}
protected bool ReplacementChangesSemantics(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode, SyntaxNode originalRoot, bool skipVerificationForCurrentNode)
{
if (this.SpeculativeSemanticModel == null)
{
// This is possible for some broken code scenarios with parse errors, bail out gracefully here.
return true;
}
SyntaxNode? previousOriginalNode = null, previousReplacedNode = null;
while (true)
{
if (!skipVerificationForCurrentNode &&
ReplacementChangesSemanticsForNode(
currentOriginalNode, currentReplacedNode,
previousOriginalNode, previousReplacedNode))
{
return true;
}
if (currentOriginalNode == originalRoot)
{
break;
}
RoslynDebug.AssertNotNull(currentOriginalNode.Parent);
RoslynDebug.AssertNotNull(currentReplacedNode.Parent);
previousOriginalNode = currentOriginalNode;
previousReplacedNode = currentReplacedNode;
currentOriginalNode = currentOriginalNode.Parent;
currentReplacedNode = currentReplacedNode.Parent;
skipVerificationForCurrentNode = skipVerificationForCurrentNode && IsParenthesizedExpression(currentReplacedNode);
}
return false;
}
/// <summary>
/// Checks whether the semantic symbols for the <see cref="OriginalExpression"/> and <see cref="ReplacedExpression"/> are non-null and compatible.
/// </summary>
/// <returns></returns>
public bool SymbolsForOriginalAndReplacedNodesAreCompatible()
{
if (this.SpeculativeSemanticModel == null)
{
// This is possible for some broken code scenarios with parse errors, bail out gracefully here.
return false;
}
return SymbolsAreCompatible(this.OriginalExpression, this.ReplacedExpression, requireNonNullSymbols: true);
}
private bool ReplacementChangesSemanticsForNode(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode, SyntaxNode? previousOriginalNode, SyntaxNode? previousReplacedNode)
{
Debug.Assert(previousOriginalNode == null || previousOriginalNode.Parent == currentOriginalNode);
Debug.Assert(previousReplacedNode == null || previousReplacedNode.Parent == currentReplacedNode);
if (!InvocationsAreCompatible(currentOriginalNode as TInvocationExpressionSyntax, currentReplacedNode as TInvocationExpressionSyntax))
return true;
if (ExpressionMightReferenceMember(currentOriginalNode))
{
// If replacing the node will result in a change in overload resolution, we won't remove it.
var originalExpression = (TExpressionSyntax)currentOriginalNode;
var newExpression = (TExpressionSyntax)currentReplacedNode;
if (ReplacementBreaksExpression(originalExpression, newExpression))
{
return true;
}
if (ReplacementBreaksSystemObjectMethodResolution(currentOriginalNode, currentReplacedNode, previousOriginalNode, previousReplacedNode))
{
return true;
}
return !ImplicitConversionsAreCompatible(originalExpression, newExpression);
}
else if (currentOriginalNode is TForEachStatementSyntax originalForEachStatement)
{
var newForEachStatement = (TForEachStatementSyntax)currentReplacedNode;
return ReplacementBreaksForEachStatement(originalForEachStatement, newForEachStatement);
}
else if (currentOriginalNode is TAttributeSyntax originalAttribute)
{
var newAttribute = (TAttributeSyntax)currentReplacedNode;
return ReplacementBreaksAttribute(originalAttribute, newAttribute);
}
else if (currentOriginalNode is TThrowStatementSyntax originalThrowStatement)
{
var newThrowStatement = (TThrowStatementSyntax)currentReplacedNode;
return ReplacementBreaksThrowStatement(originalThrowStatement, newThrowStatement);
}
else if (ReplacementChangesSemanticsForNodeLanguageSpecific(currentOriginalNode, currentReplacedNode, previousOriginalNode, previousReplacedNode))
{
return true;
}
if (currentOriginalNode is TTypeSyntax originalType)
{
var newType = (TTypeSyntax)currentReplacedNode;
return ReplacementBreaksTypeResolution(originalType, newType);
}
else if (currentOriginalNode is TExpressionSyntax originalExpression)
{
var newExpression = (TExpressionSyntax)currentReplacedNode;
if (!ImplicitConversionsAreCompatible(originalExpression, newExpression))
return true;
RoslynDebug.AssertNotNull(originalExpression);
Debug.Assert(this.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalExpression));
RoslynDebug.AssertNotNull(newExpression);
Debug.Assert(this.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newExpression));
var originalTypeInfo = this.OriginalSemanticModel.GetTypeInfo(originalExpression);
var newTypeInfo = this.SpeculativeSemanticModel.GetTypeInfo(newExpression);
// If we didn't have an error before, but now we got one, that's bad and should block conversion in all cases.
if (newTypeInfo.Type.IsErrorType() && !originalTypeInfo.Type.IsErrorType())
return true;
if (ReplacementIntroducesDisallowedNullType(originalExpression, newExpression, originalTypeInfo, newTypeInfo))
return true;
}
return false;
}
private bool MemberAccessesAreCompatible(TExpressionSyntax? originalExpression, TExpressionSyntax? newExpression)
{
// If not expressions, nothing to do here.
if (originalExpression is null && newExpression is null)
return true;
var syntaxFacts = this.SyntaxFactsService;
// it is legal to go from expr.X to X if expr is either used for static-lookup (e.g.
// `MyNs.MyType.MyStaticMember` -> `MyStaticMember`), or if it's used for instance lookup, but only if expr
// is `this` or `base`. We will ensure that the 'X' binds to the same symbol. If so, the static case is
// fine, as as long as the member is available without issue (e.g. accessibility etc.) then it doesn't
// change meaning when switching. The same holds true for an instance method with this/base as that is
// allowed to be implicit if it binds to the same exact member.
if (syntaxFacts.IsSimpleMemberAccessExpression(originalExpression) &&
!syntaxFacts.IsSimpleMemberAccessExpression(newExpression))
{
// if we don't even have a name after simplification, this is never ok.
if (!syntaxFacts.IsSimpleName(newExpression))
return false;
// The symbols before/after must be exactly the same (this also ensures that a base access of some
// overridden method will be preserved as otherwise the symbols are different).
if (!SymbolsAreCompatible(originalExpression, newExpression))
return false;
// If static became instance or instance became static, consider that a change in semantics. Note: this
// does mean a change from extension->instance call (or vice versa) will be seen as a change in
// semantics. We could potentially support this, but we'd have to do the analysis the instance invoked
// on and the instance passed as the first parameter are identical.
var originalIsStaticAccess = IsStaticAccess(OriginalSemanticModel.GetSymbolInfo(originalExpression, CancellationToken).Symbol);
var replacedIsStaticAccess = IsStaticAccess(this.SpeculativeSemanticModel.GetSymbolInfo(newExpression, CancellationToken).Symbol);
if (originalIsStaticAccess != replacedIsStaticAccess)
return false;
// When binding expr.A, if we didn't bind 'expr' binding to a type, namespace, or other static
// thing, then we bound to an instance symbol. It's then only ok to remove 'expr' if 'expr' is
// this/base.
if (!originalIsStaticAccess)
{
var originalExpressionOfMemberAccess = syntaxFacts.GetExpressionOfMemberAccessExpression(originalExpression);
if (!CanAccessInstanceMemberThrough((TExpressionSyntax?)originalExpressionOfMemberAccess))
return false;
}
}
return true;
}
private static bool IsStaticAccess(ISymbol? symbol)
=> symbol is INamespaceOrTypeSymbol or { IsStatic: true };
private bool InvocationsAreCompatible(TInvocationExpressionSyntax? originalInvocation, TInvocationExpressionSyntax? newInvocation)
{
// If not invocations, nothing to do here.
if (originalInvocation is null && newInvocation is null)
return true;
if (originalInvocation is not null)
{
// Invocations must stay invocations after update.
if (newInvocation is null)
return false;
var syntaxFacts = this.SyntaxFactsService;
if (!MemberAccessesAreCompatible(
syntaxFacts.GetExpressionOfInvocationExpression(originalInvocation) as TExpressionSyntax,
syntaxFacts.GetExpressionOfInvocationExpression(newInvocation) as TExpressionSyntax))
{
return false;
}
// Add more invocation tests here.
}
// Add more operation tests here.
return true;
}
/// <summary>
/// Determine if removing the cast could cause the semantics of System.Object method call to change.
/// E.g. Dim b = CStr(1).GetType() is necessary, but the GetType method symbol info resolves to the same with or without the cast.
/// </summary>
private bool ReplacementBreaksSystemObjectMethodResolution(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode, [NotNullWhen(true)] SyntaxNode? previousOriginalNode, [NotNullWhen(true)] SyntaxNode? previousReplacedNode)
{
if (previousOriginalNode != null && previousReplacedNode != null)
{
var originalExpressionSymbol = this.OriginalSemanticModel.GetSymbolInfo(currentOriginalNode).Symbol;
var replacedExpressionSymbol = this.SpeculativeSemanticModel.GetSymbolInfo(currentReplacedNode).Symbol;
if (IsSymbolSystemObjectInstanceMethod(originalExpressionSymbol) && IsSymbolSystemObjectInstanceMethod(replacedExpressionSymbol))
{
var previousOriginalType = this.OriginalSemanticModel.GetTypeInfo(previousOriginalNode).Type;
var previousReplacedType = this.SpeculativeSemanticModel.GetTypeInfo(previousReplacedNode).Type;
if (previousReplacedType != null && previousOriginalType != null)
{
return !previousReplacedType.InheritsFromOrEquals(previousOriginalType);
}
}
}
return false;
}
/// <summary>
/// Determines if the symbol is a non-overridable, non static method on System.Object (e.g. GetType)
/// </summary>
private static bool IsSymbolSystemObjectInstanceMethod([NotNullWhen(true)] ISymbol? symbol)
{
return symbol != null
&& symbol.IsKind(SymbolKind.Method)
&& symbol.ContainingType.SpecialType == SpecialType.System_Object
&& !symbol.IsOverridable()
&& !symbol.IsStaticType();
}
private bool ReplacementBreaksAttribute(TAttributeSyntax attribute, TAttributeSyntax newAttribute)
{
var attributeSym = this.OriginalSemanticModel.GetSymbolInfo(attribute).Symbol;
var newAttributeSym = this.SpeculativeSemanticModel.GetSymbolInfo(newAttribute).Symbol;
return !SymbolsAreCompatible(attributeSym, newAttributeSym);
}
private bool ReplacementBreaksForEachStatement(TForEachStatementSyntax forEachStatement, TForEachStatementSyntax newForEachStatement)
{
var forEachExpression = GetForEachStatementExpression(forEachStatement);
if (forEachExpression.IsMissing ||
!forEachExpression.Span.Contains(OriginalExpression.SpanStart))
{
return false;
}
GetForEachSymbols(this.OriginalSemanticModel, forEachStatement, out var originalGetEnumerator, out var originalElementType, out var originalLocalVariables);
GetForEachSymbols(this.SpeculativeSemanticModel, newForEachStatement, out var newGetEnumerator, out var newElementType, out var newLocalVariables);
// inferred variable type compatible
if (IsForEachTypeInferred(forEachStatement, OriginalSemanticModel))
{
if (originalLocalVariables.Length != newLocalVariables.Length)
return true;
for (int i = 0, n = originalLocalVariables.Length; i < n; i++)
{
if (!SymbolsAreCompatible(originalLocalVariables[i].Type, newLocalVariables[i].Type))
return true;
}
}
var newForEachExpression = GetForEachStatementExpression(newForEachStatement);
if (ReplacementBreaksForEachGetEnumerator(originalGetEnumerator, newGetEnumerator, newForEachExpression) ||
!ForEachConversionsAreCompatible(this.OriginalSemanticModel, forEachStatement, this.SpeculativeSemanticModel, newForEachStatement) ||
!SymbolsAreCompatible(originalElementType, newElementType))
{
return true;
}
return false;
}
private bool ReplacementBreaksForEachGetEnumerator(IMethodSymbol getEnumerator, IMethodSymbol newGetEnumerator, TExpressionSyntax newForEachStatementExpression)
{
if (getEnumerator == null && newGetEnumerator == null)
{
return false;
}
if (getEnumerator == null || newGetEnumerator == null)
{
return true;
}
if (getEnumerator.ToSignatureDisplayString() != newGetEnumerator.ToSignatureDisplayString())
{
// Note this is likely an interface member from IEnumerable but the new member may be a
// GetEnumerator method on a specific type.
if (getEnumerator.IsImplementableMember())
{
var expressionType = this.SpeculativeSemanticModel.GetTypeInfo(newForEachStatementExpression, CancellationToken).ConvertedType;
if (expressionType != null)
{
var implementationMember = expressionType.FindImplementationForInterfaceMember(getEnumerator);
if (implementationMember != null)
{
if (implementationMember.ToSignatureDisplayString() != newGetEnumerator.ToSignatureDisplayString())
{
return false;
}
}
}
}
return true;
}
return false;
}
private bool ReplacementBreaksThrowStatement(TThrowStatementSyntax originalThrowStatement, TThrowStatementSyntax newThrowStatement)
{
var originalThrowExpression = GetThrowStatementExpression(originalThrowStatement);
var originalThrowExpressionType = this.OriginalSemanticModel.GetTypeInfo(originalThrowExpression).Type;
var newThrowExpression = GetThrowStatementExpression(newThrowStatement);
var newThrowExpressionType = this.SpeculativeSemanticModel.GetTypeInfo(newThrowExpression).Type;
// C# language specification requires that type of the expression passed to ThrowStatement is or derives from System.Exception.
return originalThrowExpressionType.IsOrDerivesFromExceptionType(this.OriginalSemanticModel.Compilation) !=
newThrowExpressionType.IsOrDerivesFromExceptionType(this.SpeculativeSemanticModel.Compilation);
}
private bool ReplacementBreaksTypeResolution(TTypeSyntax type, TTypeSyntax newType, bool useSpeculativeModel = true)
{
var symbol = this.OriginalSemanticModel.GetSymbolInfo(type).Symbol;
ISymbol? newSymbol;
if (useSpeculativeModel)
{
newSymbol = this.SpeculativeSemanticModel.GetSymbolInfo(newType, CancellationToken).Symbol;
}
else
{
var bindingOption = IsInNamespaceOrTypeContext(type) ? SpeculativeBindingOption.BindAsTypeOrNamespace : SpeculativeBindingOption.BindAsExpression;
newSymbol = this.OriginalSemanticModel.GetSpeculativeSymbolInfo(type.SpanStart, newType, bindingOption).Symbol;
}
return symbol != null && !SymbolsAreCompatible(symbol, newSymbol);
}
private static bool IsDelegateInvoke(ISymbol symbol)
{
return symbol.Kind == SymbolKind.Method &&
((IMethodSymbol)symbol).MethodKind == MethodKind.DelegateInvoke;
}
private static bool IsAnonymousDelegateInvoke(ISymbol symbol)
{
return IsDelegateInvoke(symbol) &&
symbol.ContainingType != null &&
symbol.ContainingType.IsAnonymousType();
}
private bool ReplacementBreaksExpression(TExpressionSyntax expression, TExpressionSyntax newExpression)
{
var originalSymbolInfo = OriginalSemanticModel.GetSymbolInfo(expression);
if (_failOnOverloadResolutionFailuresInOriginalCode && originalSymbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure)
{
return true;
}
var newSymbolInfo = this.SpeculativeSemanticModel.GetSymbolInfo(node: newExpression);
var symbol = originalSymbolInfo.Symbol;
var newSymbol = newSymbolInfo.Symbol;
if (SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo))
{
// Original and new symbols for the invocation expression are compatible.
// However, if the symbols are interface members and if the receiver symbol for one of the expressions is a possible ValueType type parameter,
// and the other one is not, then there might be a boxing conversion at runtime which causes different runtime behavior.
if (symbol.IsImplementableMember())
{
if (IsReceiverNonUniquePossibleValueTypeParam(expression, this.OriginalSemanticModel) !=
IsReceiverNonUniquePossibleValueTypeParam(newExpression, this.SpeculativeSemanticModel))
{
return true;
}
}
return false;
}
if (symbol == null || newSymbol == null || originalSymbolInfo.CandidateReason != newSymbolInfo.CandidateReason)
{
return true;
}
if (newSymbol.IsOverride)
{
for (var overriddenMember = newSymbol.GetOverriddenMember(); overriddenMember != null; overriddenMember = overriddenMember.GetOverriddenMember())
{
if (symbol.Equals(overriddenMember))
return !SymbolsHaveCompatibleParameterLists(symbol, newSymbol, expression);
}
}
if (symbol.IsImplementableMember() &&
IsCompatibleInterfaceMemberImplementation(
symbol, newSymbol, expression, newExpression, this.SpeculativeSemanticModel))
{
return false;
}
// Allow speculated invocation expression to bind to a different method symbol if the method's containing type is a delegate type
// which has a delegate variance conversion to/from the original method's containing delegate type.
if (newSymbol.ContainingType.IsDelegateType() &&
symbol.ContainingType.IsDelegateType() &&
IsReferenceConversion(this.OriginalSemanticModel.Compilation, newSymbol.ContainingType, symbol.ContainingType))
{
return false;
}
// Heuristic: If we now bind to an anonymous delegate's invoke method, assume that
// this isn't a change in overload resolution.
if (IsAnonymousDelegateInvoke(newSymbol))
{
return false;
}
return true;
}
protected bool ReplacementBreaksCompoundAssignment(
TExpressionSyntax originalLeft,
TExpressionSyntax originalRight,
TExpressionSyntax newLeft,
TExpressionSyntax newRight)
{
var originalTargetType = this.OriginalSemanticModel.GetTypeInfo(originalLeft).Type;
if (originalTargetType != null)
{
var newTargetType = this.SpeculativeSemanticModel.GetTypeInfo(newLeft).Type;
return !SymbolsAreCompatible(originalTargetType, newTargetType) ||
!ImplicitConversionsAreCompatible(originalRight, originalTargetType, newRight, newTargetType!);
}
return false;
}
private bool IsCompatibleInterfaceMemberImplementation(
ISymbol symbol,
ISymbol newSymbol,
TExpressionSyntax originalExpression,
TExpressionSyntax newExpression,
SemanticModel speculativeSemanticModel)
{
// In general, we don't want to remove casts to interfaces. It may have subtle changes in behavior,
// especially if the types in question change in the future. For example, if a type becomes non-sealed or a
// new interface impl is introduced, we may subtly break things.
//
// The only cases where we feel confident enough to elide the cast are:
//
// 1. When we have an Array/Delegate/Enum. These are such core types, and cannot be changed by teh user,
// that we can trust their impls to not change.
// 2. We have one of the builtin structs (like int). These are such core types, and cannot be changed by teh
// user, that we can trust their impls to not change.
// 3. if we have a struct and we know we have a fresh copy of it. In that case, boxing the struct to the
// interface doesn't serve any purpose.
var newSymbolContainingType = newSymbol.ContainingType;
if (newSymbolContainingType == null)
return false;
var newReceiver = GetReceiver(newExpression);
var newReceiverType = newReceiver != null
? speculativeSemanticModel.GetTypeInfo(newReceiver).ConvertedType
: newSymbolContainingType;
if (newReceiverType == null)
return false;
var implementationMember = newSymbolContainingType.FindImplementationForInterfaceMember(symbol);
if (implementationMember == null)
return false;
if (!newSymbol.Equals(implementationMember))
return false;
if (!SymbolsHaveCompatibleParameterLists(symbol, implementationMember, originalExpression))
return false;
if (newReceiverType.IsValueType)
{
// Presume builtin value types are all immutable, and thus will have the same semantics when you call
// interface members on them directly instead of through a boxed copy.
if (newReceiverType.SpecialType != SpecialType.None)
return true;
// For non-builtins, only remove the boxing if we know we have a copy already.
return newReceiver != null && IsReceiverUniqueInstance(newReceiver, speculativeSemanticModel);
}
return newSymbolContainingType.SpecialType is SpecialType.System_Array or
SpecialType.System_Delegate or
SpecialType.System_Enum or
SpecialType.System_String;
}
private bool IsReceiverNonUniquePossibleValueTypeParam(TExpressionSyntax invocation, SemanticModel semanticModel)
{
var receiver = GetReceiver(invocation);
if (receiver != null)
{
var receiverType = semanticModel.GetTypeInfo(receiver).Type;
if (receiverType.IsKind(SymbolKind.TypeParameter) && !receiverType.IsReferenceType)
{
return !IsReceiverUniqueInstance(receiver, semanticModel);
}
}
return false;
}
// Returns true if the given receiver expression for an invocation represents a unique copy of the underlying
// object that is not referenced by any other variable. For example, if the receiver expression is produced by a
// method call, property, or indexer, then it will be a fresh receiver in the case of value types.
private static bool IsReceiverUniqueInstance(TExpressionSyntax receiver, SemanticModel semanticModel)
{
var receiverSymbol = semanticModel.GetSymbolInfo(receiver).GetAnySymbol();
if (receiverSymbol == null)
return false;
return receiverSymbol.IsKind(SymbolKind.Method) ||
receiverSymbol.IsIndexer() ||
receiverSymbol.IsKind(SymbolKind.Property);
}
private bool SymbolsHaveCompatibleParameterLists(ISymbol originalSymbol, ISymbol newSymbol, TExpressionSyntax originalInvocation)
{
if (originalSymbol.IsKind(SymbolKind.Method) || originalSymbol.IsIndexer())
{
var specifiedArguments = GetArguments(originalInvocation);
if (!specifiedArguments.IsDefault)
{
var symbolParameters = originalSymbol.GetParameters();
var newSymbolParameters = newSymbol.GetParameters();
return AreCompatibleParameterLists(specifiedArguments, symbolParameters, newSymbolParameters);
}
}
return true;
}
private bool AreCompatibleParameterLists(
ImmutableArray<TArgumentSyntax> specifiedArguments,
ImmutableArray<IParameterSymbol> signature1Parameters,
ImmutableArray<IParameterSymbol> signature2Parameters)
{
Debug.Assert(signature1Parameters.Length == signature2Parameters.Length);
Debug.Assert(specifiedArguments.Length <= signature1Parameters.Length ||
(signature1Parameters.Length > 0 && !signature1Parameters.Last().IsParams));
if (signature1Parameters.Length != signature2Parameters.Length)
{
return false;
}
// If there aren't any parameters, we're OK.
if (signature1Parameters.Length == 0)
{
return true;
}
// To ensure that the second parameter list is called in the same way as the
// first, we need to use the specified arguments to bail out if...
//
// * A named argument doesn't have a corresponding parameter in the
// in either parameter list, or...
//
// * A named argument matches a parameter that is in a different position
// in the two parameter lists.
//
// After checking the specified arguments, we walk the unspecified parameters
// in both parameter lists to ensure that they have matching default values.
var specifiedParameters1 = new List<IParameterSymbol>();
var specifiedParameters2 = new List<IParameterSymbol>();
for (var i = 0; i < specifiedArguments.Length; i++)
{
var argument = specifiedArguments[i];
// Handle named argument
if (IsNamedArgument(argument))
{
var name = GetNamedArgumentIdentifierValueText(argument);
var parameter1 = signature1Parameters.FirstOrDefault(p => p.Name == name);
RoslynDebug.AssertNotNull(parameter1);
var parameter2 = signature2Parameters.FirstOrDefault(p => p.Name == name);
if (parameter2 == null)
{
return false;
}
if (signature1Parameters.IndexOf(parameter1) != signature2Parameters.IndexOf(parameter2))
{
return false;
}
specifiedParameters1.Add(parameter1);
specifiedParameters2.Add(parameter2);
}
else
{
// otherwise, treat the argument positionally, taking care to properly
// handle params parameters.
if (i < signature1Parameters.Length)
{
specifiedParameters1.Add(signature1Parameters[i]);
specifiedParameters2.Add(signature2Parameters[i]);
}
}
}
// At this point, we can safely assume that specifiedParameters1 and signature2Parameters
// contain parameters that appear at the same positions in their respective signatures
// because we bailed out if named arguments referred to parameters at different positions.
//
// Now we walk the unspecified parameters to ensure that they have the same default
// values.
for (var i = 0; i < signature1Parameters.Length; i++)
{
var parameter1 = signature1Parameters[i];
if (specifiedParameters1.Contains(parameter1))
{
continue;
}
var parameter2 = signature2Parameters[i];
Debug.Assert(parameter1.HasExplicitDefaultValue, "Expected all unspecified parameter to have default values");
Debug.Assert(parameter1.HasExplicitDefaultValue == parameter2.HasExplicitDefaultValue);
if (parameter1.HasExplicitDefaultValue && parameter2.HasExplicitDefaultValue)
{
if (!object.Equals(parameter2.ExplicitDefaultValue, parameter1.ExplicitDefaultValue))
{
return false;
}
if (object.Equals(parameter1.ExplicitDefaultValue, 0.0))
{
RoslynDebug.Assert(object.Equals(parameter2.ExplicitDefaultValue, 0.0));
var isParam1DefaultValueNegativeZero = double.IsNegativeInfinity(1.0 / (double)parameter1.ExplicitDefaultValue);
var isParam2DefaultValueNegativeZero = double.IsNegativeInfinity(1.0 / (double)parameter2.ExplicitDefaultValue);
if (isParam1DefaultValueNegativeZero != isParam2DefaultValueNegativeZero)
{
return false;
}
}
}
}
return true;
}
protected void GetConversions(
TExpressionSyntax originalExpression,
ITypeSymbol originalTargetType,
TExpressionSyntax newExpression,
ITypeSymbol newTargetType,
out TConversion? originalConversion,
out TConversion? newConversion)
{
originalConversion = null;
newConversion = null;
if (this.OriginalSemanticModel.GetTypeInfo(originalExpression).Type != null &&
this.SpeculativeSemanticModel.GetTypeInfo(newExpression).Type != null)
{
originalConversion = ClassifyConversion(this.OriginalSemanticModel, originalExpression, originalTargetType);
newConversion = ClassifyConversion(this.SpeculativeSemanticModel, newExpression, newTargetType);
}
else
{
var originalConvertedTypeSymbol = this.OriginalSemanticModel.GetTypeInfo(originalExpression).ConvertedType;
if (originalConvertedTypeSymbol != null)
{
originalConversion = ClassifyConversion(this.OriginalSemanticModel, originalConvertedTypeSymbol, originalTargetType);
}
var newConvertedTypeSymbol = this.SpeculativeSemanticModel.GetTypeInfo(newExpression).ConvertedType;
if (newConvertedTypeSymbol != null)
{
newConversion = ClassifyConversion(this.SpeculativeSemanticModel, newConvertedTypeSymbol, newTargetType);
}
}
}
}
|