// 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 enable #pragma warning disable RS1024 // Use 'SymbolEqualityComparer' when comparing symbols (https://github.com/dotnet/roslyn/issues/78583) using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; namespace Microsoft.CodeAnalysis.Shared.Utilities; internal sealed class SignatureComparer { public static readonly SignatureComparer Instance = new(SymbolEquivalenceComparer.Instance); public static readonly SignatureComparer IgnoreAssembliesInstance = new(SymbolEquivalenceComparer.IgnoreAssembliesInstance); private readonly SymbolEquivalenceComparer _symbolEquivalenceComparer; private SignatureComparer(SymbolEquivalenceComparer symbolEquivalenceComparer) => _symbolEquivalenceComparer = symbolEquivalenceComparer; private IEqualityComparer<IParameterSymbol> ParameterEquivalenceComparer => _symbolEquivalenceComparer.ParameterEquivalenceComparer; private IEqualityComparer<ITypeSymbol> SignatureTypeEquivalenceComparer => _symbolEquivalenceComparer.SignatureTypeEquivalenceComparer; public bool HaveSameSignature(ISymbol symbol1, ISymbol symbol2, bool caseSensitive) { // NOTE - we're deliberately using reference equality here for speed. if (symbol1 == symbol2) return true; if (symbol1 == null || symbol2 == null) return false; if (symbol1.Kind != symbol2.Kind) return false; return symbol1.Kind switch { SymbolKind.Method => HaveSameSignature((IMethodSymbol)symbol1, (IMethodSymbol)symbol2, caseSensitive), SymbolKind.Property => HaveSameSignature((IPropertySymbol)symbol1, (IPropertySymbol)symbol2, caseSensitive), SymbolKind.Event => HaveSameSignature((IEventSymbol)symbol1, (IEventSymbol)symbol2, caseSensitive), _ => true, }; } private static bool HaveSameSignature(IEventSymbol event1, IEventSymbol event2, bool caseSensitive) => IdentifiersMatch(event1.Name, event2.Name, caseSensitive); public bool HaveSameSignature(IPropertySymbol property1, IPropertySymbol property2, bool caseSensitive) { if (!IdentifiersMatch(property1.Name, property2.Name, caseSensitive) || property1.Parameters.Length != property2.Parameters.Length || property1.IsIndexer != property2.IsIndexer) { return false; } return property1.Parameters.SequenceEqual( property2.Parameters, this.ParameterEquivalenceComparer); } public static bool BadPropertyAccessor(IMethodSymbol? method1, IMethodSymbol? method2) { return method1 != null && (method2 == null || method2.DeclaredAccessibility != Accessibility.Public); } public bool HaveSameSignature(IMethodSymbol method1, IMethodSymbol method2, bool caseSensitive, bool compareParameterName = false, bool isParameterCaseSensitive = false) { if ((method1.MethodKind == MethodKind.AnonymousFunction) != (method2.MethodKind == MethodKind.AnonymousFunction)) { return false; } if (method1.MethodKind != MethodKind.AnonymousFunction) { if (!IdentifiersMatch(method1.Name, method2.Name, caseSensitive)) return false; } if (method1.MethodKind != method2.MethodKind || method1.Arity != method2.Arity) { return false; } return HaveSameSignature(method1.Parameters, method2.Parameters, compareParameterName, isParameterCaseSensitive); } private static bool IdentifiersMatch(string identifier1, string identifier2, bool caseSensitive) { return caseSensitive ? identifier1 == identifier2 : string.Equals(identifier1, identifier2, StringComparison.OrdinalIgnoreCase); } public bool HaveSameSignature( ImmutableArray<IParameterSymbol> parameters1, ImmutableArray<IParameterSymbol> parameters2) { if (parameters1.Length != parameters2.Length) return false; return parameters1.SequenceEqual(parameters2, this.ParameterEquivalenceComparer); } public bool HaveSameSignature( ImmutableArray<IParameterSymbol> parameters1, ImmutableArray<IParameterSymbol> parameters2, bool compareParameterName, bool isCaseSensitive) { if (parameters1.Length != parameters2.Length) return false; for (var i = 0; i < parameters1.Length; ++i) { if (!_symbolEquivalenceComparer.ParameterEquivalenceComparer.Equals(parameters1[i], parameters2[i], compareParameterName, isCaseSensitive)) return false; } return true; } public bool HaveSameSignatureAndConstraintsAndReturnTypeAndAccessors(ISymbol symbol1, ISymbol symbol2, bool caseSensitive) { // NOTE - we're deliberately using reference equality here for speed. if (symbol1 == symbol2) return true; if (!HaveSameSignatureAndConstraintsAndReturnType(symbol1, symbol2, caseSensitive)) return false; if (symbol1 is IPropertySymbol property1 && symbol2 is IPropertySymbol property2) return HaveSameAccessors(property1, property2); return true; } public bool HaveSameSignatureAndConstraintsAndReturnType(ISymbol symbol1, ISymbol symbol2, bool caseSensitive) { // NOTE - we're deliberately using reference equality here for speed. if (symbol1 == symbol2) return true; if (!HaveSameSignature(symbol1, symbol2, caseSensitive)) return false; switch (symbol1.Kind) { case SymbolKind.Method: var method1 = (IMethodSymbol)symbol1; var method2 = (IMethodSymbol)symbol2; return HaveSameSignatureAndConstraintsAndReturnType(method1, method2); case SymbolKind.Property: var property1 = (IPropertySymbol)symbol1; var property2 = (IPropertySymbol)symbol2; return property1.ReturnsByRef == property2.ReturnsByRef && property1.ReturnsByRefReadonly == property2.ReturnsByRefReadonly && this.SignatureTypeEquivalenceComparer.Equals(property1.Type, property2.Type); case SymbolKind.Event: var ev1 = (IEventSymbol)symbol1; var ev2 = (IEventSymbol)symbol2; return HaveSameReturnType(ev1, ev2); } return true; } private static bool HaveSameAccessors(IPropertySymbol property1, IPropertySymbol property2) { if (property1.ContainingType == null || property1.ContainingType.TypeKind == TypeKind.Interface) { if (BadPropertyAccessor(property1.GetMethod, property2.GetMethod) || BadPropertyAccessor(property1.SetMethod, property2.SetMethod)) { return false; } } if (property2.ContainingType == null || property2.ContainingType.TypeKind == TypeKind.Interface) { if (BadPropertyAccessor(property2.GetMethod, property1.GetMethod) || BadPropertyAccessor(property2.SetMethod, property1.SetMethod)) { return false; } } return true; } private bool HaveSameSignatureAndConstraintsAndReturnType(IMethodSymbol method1, IMethodSymbol method2) { if (method1.ReturnsVoid != method2.ReturnsVoid || method1.ReturnsByRef != method2.ReturnsByRef || method1.ReturnsByRefReadonly != method2.ReturnsByRefReadonly) { return false; } if (!method1.ReturnsVoid && !this.SignatureTypeEquivalenceComparer.Equals(method1.ReturnType, method2.ReturnType)) return false; for (var i = 0; i < method1.TypeParameters.Length; i++) { var typeParameter1 = method1.TypeParameters[i]; var typeParameter2 = method2.TypeParameters[i]; if (!HaveSameConstraints(typeParameter1, typeParameter2)) return false; } return true; } private bool HaveSameConstraints(ITypeParameterSymbol typeParameter1, ITypeParameterSymbol typeParameter2) { if (typeParameter1.HasConstructorConstraint != typeParameter2.HasConstructorConstraint || typeParameter1.HasReferenceTypeConstraint != typeParameter2.HasReferenceTypeConstraint || typeParameter1.HasValueTypeConstraint != typeParameter2.HasValueTypeConstraint || typeParameter1.AllowsRefLikeType != typeParameter2.AllowsRefLikeType) { return false; } if (typeParameter1.ConstraintTypes.Length != typeParameter2.ConstraintTypes.Length) { return false; } return typeParameter1.ConstraintTypes.SetEquals( typeParameter2.ConstraintTypes, this.SignatureTypeEquivalenceComparer); } private bool HaveSameReturnType(IEventSymbol ev1, IEventSymbol ev2) => this.SignatureTypeEquivalenceComparer.Equals(ev1.Type, ev2.Type); } |