File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Extensions\SemanticEquivalence.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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 Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Extensions;
 
internal static class SemanticEquivalence
{
    public static bool AreEquivalent(SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2)
        => AreEquivalent(semanticModel, semanticModel, node1, node2);
 
    public static bool AreEquivalent(
        SemanticModel semanticModel1,
        SemanticModel semanticModel2,
        SyntaxNode? node1,
        SyntaxNode? node2,
        Func<SyntaxNode, bool>? predicate = null)
    {
        // First check for syntactic equivalency.  If two nodes aren't structurally equivalent,
        // then they're not semantically equivalent.
        if (node1 == null && node2 == null)
        {
            return true;
        }
 
        if (node1 == null || node2 == null)
        {
            return false;
        }
 
        if (!node1.IsEquivalentTo(node2, topLevel: false))
        {
            return false;
        }
 
        // From this point on we can assume the tree structure is the same.  So no need to check
        // kinds, child counts or token contents.
        return AreSemanticallyEquivalentWorker(
            semanticModel1, semanticModel2, node1, node2, predicate);
    }
 
    private static bool AreSemanticallyEquivalentWorker(
        SemanticModel semanticModel1,
        SemanticModel semanticModel2,
        SyntaxNode node1,
        SyntaxNode node2,
        Func<SyntaxNode, bool>? predicate)
    {
        if (node1 == node2)
        {
            return true;
        }
 
        if (predicate == null || predicate(node1))
        {
            var info1 = semanticModel1.GetSymbolInfo(node1);
            var info2 = semanticModel2.GetSymbolInfo(node2);
 
            if (!AreEquals(semanticModel1, semanticModel2, info1, info2))
            {
                return false;
            }
        }
 
        // Original expression and current node being semantically equivalent isn't enough when the original expression 
        // is a member access via instance reference (either implicit or explicit), the check only ensures that the expression
        // and current node are both backed by the same member symbol. So in this case, in addition to SemanticEquivalence check, 
        // we also check if expression and current node are both instance member access.
        //
        // For example, even though the first `c` binds to a field and we are introducing a local for it,
        // we don't want other references to that field to be replaced as well (i.e. the second `c` in the expression).
        //
        //  class C
        //  {
        //      C c;
        //      void Test()
        //      {
        //          var x = [|c|].c;
        //      }
        //  }
        var originalOperation = semanticModel1.GetOperation(node1);
        if (originalOperation != null && IsInstanceMemberReference(originalOperation))
        {
            var currentOperation = semanticModel2.GetOperation(node2);
 
            if (currentOperation is null || !IsInstanceMemberReference(currentOperation))
            {
                return false;
            }
        }
 
        var e1 = node1.ChildNodesAndTokens().GetEnumerator();
        var e2 = node2.ChildNodesAndTokens().GetEnumerator();
 
        while (true)
        {
            var b1 = e1.MoveNext();
            var b2 = e2.MoveNext();
 
            Contract.ThrowIfTrue(b1 != b2);
 
            if (b1 == false)
            {
                return true;
            }
 
            var c1 = e1.Current;
            var c2 = e2.Current;
 
            if (c1.AsNode(out var c1Node) && c2.AsNode(out var c2Node))
            {
                if (!AreSemanticallyEquivalentWorker(semanticModel1, semanticModel2, c1Node, c2Node, predicate))
                {
                    return false;
                }
            }
        }
    }
 
    private static bool IsInstanceMemberReference(IOperation operation)
        => operation is IMemberReferenceOperation { Instance.Kind: OperationKind.InstanceReference };
 
    private static bool AreEquals(
        SemanticModel semanticModel1,
        SemanticModel semanticModel2,
        SymbolInfo info1,
        SymbolInfo info2)
    {
        return semanticModel1 == semanticModel2
            ? EqualityComparer<ISymbol?>.Default.Equals(info1.Symbol, info2.Symbol)
            : SymbolEquivalenceComparer.Instance.Equals(info1.Symbol, info2.Symbol);
    }
}