// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions
{
internal static class IMethodSymbolExtensions
{
public static bool CompatibleSignatureToDelegate(this IMethodSymbol method, INamedTypeSymbol delegateType)
{
Contract.ThrowIfFalse(delegateType.TypeKind == TypeKind.Delegate);
var invoke = delegateType.DelegateInvokeMethod;
if (invoke == null)
{
// It's possible to get events with no invoke method from metadata. We will assume
// that no method can be an event handler for one.
return false;
}
if (method.Parameters.Length != invoke.Parameters.Length)
{
return false;
}
if (method.ReturnsVoid != invoke.ReturnsVoid)
{
return false;
}
if (!method.ReturnType.InheritsFromOrEquals(invoke.ReturnType))
{
return false;
}
for (int i = 0; i < method.Parameters.Length; i++)
{
if (!invoke.Parameters[i].Type.InheritsFromOrEquals(method.Parameters[i].Type))
{
return false;
}
}
return true;
}
///
/// Returns the methodSymbol and any partial parts.
///
public static ImmutableArray GetAllMethodSymbolsOfPartialParts(this IMethodSymbol method)
{
if (method.PartialDefinitionPart != null)
{
Debug.Assert(method.PartialImplementationPart == null && method.PartialDefinitionPart != method);
return ImmutableArray.Create(method, method.PartialDefinitionPart);
}
else if (method.PartialImplementationPart != null)
{
Debug.Assert(method.PartialImplementationPart != method);
return ImmutableArray.Create(method.PartialImplementationPart, method);
}
else
{
return ImmutableArray.Create(method);
}
}
public static IMethodSymbol RenameTypeParameters(this IMethodSymbol method, IList newNames)
{
if (method.TypeParameters.Select(t => t.Name).SequenceEqual(newNames))
{
return method;
}
var typeGenerator = new TypeGenerator();
var updatedTypeParameters = RenameTypeParameters(
method.TypeParameters, newNames, typeGenerator);
var mapping = new Dictionary();
for (int i = 0; i < method.TypeParameters.Length; i++)
{
mapping[method.TypeParameters[i]] = updatedTypeParameters[i];
}
return CodeGenerationSymbolFactory.CreateMethodSymbol(
method.ContainingType,
method.GetAttributes(),
method.DeclaredAccessibility,
method.GetSymbolModifiers(),
method.ReturnType.SubstituteTypes(mapping, typeGenerator),
method.RefKind,
method.ExplicitInterfaceImplementations,
method.Name,
updatedTypeParameters,
method.Parameters.SelectAsArray(p =>
CodeGenerationSymbolFactory.CreateParameterSymbol(p.GetAttributes(), p.RefKind, p.IsParams, p.Type.SubstituteTypes(mapping, typeGenerator), p.Name, p.IsOptional,
p.HasExplicitDefaultValue, p.HasExplicitDefaultValue ? p.ExplicitDefaultValue : null)));
}
public static IMethodSymbol RenameParameters(
this IMethodSymbol method, IList parameterNames)
{
var parameterList = method.Parameters;
if (parameterList.Select(p => p.Name).SequenceEqual(parameterNames))
{
return method;
}
var parameters = parameterList.RenameParameters(parameterNames);
return CodeGenerationSymbolFactory.CreateMethodSymbol(
method.ContainingType,
method.GetAttributes(),
method.DeclaredAccessibility,
method.GetSymbolModifiers(),
method.ReturnType,
method.RefKind,
method.ExplicitInterfaceImplementations,
method.Name,
method.TypeParameters,
parameters);
}
private static ImmutableArray RenameTypeParameters(
ImmutableArray typeParameters,
IList newNames,
ITypeGenerator typeGenerator)
{
// We generate the type parameter in two passes. The first creates the new type
// parameter. The second updates the constraints to point at this new type parameter.
var newTypeParameters = new List();
var mapping = new Dictionary();
for (int i = 0; i < typeParameters.Length; i++)
{
var typeParameter = typeParameters[i];
var newTypeParameter = new CodeGenerationTypeParameterSymbol(
typeParameter.ContainingType,
typeParameter.GetAttributes(),
typeParameter.Variance,
newNames[i],
typeParameter.ConstraintTypes,
typeParameter.HasConstructorConstraint,
typeParameter.HasReferenceTypeConstraint,
typeParameter.HasValueTypeConstraint,
typeParameter.HasUnmanagedTypeConstraint,
typeParameter.Ordinal);
newTypeParameters.Add(newTypeParameter);
mapping[typeParameter] = newTypeParameter;
}
// Now we update the constraints.
foreach (var newTypeParameter in newTypeParameters)
{
newTypeParameter.ConstraintTypes = ImmutableArray.CreateRange(newTypeParameter.ConstraintTypes, t => t.SubstituteTypes(mapping, typeGenerator));
}
return newTypeParameters.Cast().ToImmutableArray();
}
public static IMethodSymbol EnsureNonConflictingNames(
this IMethodSymbol method, INamedTypeSymbol containingType, ISyntaxFactsService syntaxFacts, CancellationToken cancellationToken)
{
// The method's type parameters may conflict with the type parameters in the type
// we're generating into. In that case, rename them.
var parameterNames = NameGenerator.EnsureUniqueness(
method.Parameters.Select(p => p.Name).ToList(), isCaseSensitive: syntaxFacts.IsCaseSensitive);
var outerTypeParameterNames =
containingType.GetAllTypeParameters()
.Select(tp => tp.Name)
.Concat(method.Name)
.Concat(containingType.Name);
var unusableNames = parameterNames.Concat(outerTypeParameterNames).ToSet(
syntaxFacts.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase);
var newTypeParameterNames = NameGenerator.EnsureUniqueness(
method.TypeParameters.Select(tp => tp.Name).ToList(),
n => !unusableNames.Contains(n));
var updatedMethod = method.RenameTypeParameters(newTypeParameterNames);
return updatedMethod.RenameParameters(parameterNames);
}
public static IMethodSymbol RemoveInaccessibleAttributesAndAttributesOfTypes(
this IMethodSymbol method, ISymbol accessibleWithin,
params INamedTypeSymbol[] removeAttributeTypes)
{
bool shouldRemoveAttribute(AttributeData a) =>
removeAttributeTypes.Any(attr => attr != null && attr.Equals(a.AttributeClass)) || !a.AttributeClass.IsAccessibleWithin(accessibleWithin);
return method.RemoveAttributesCore(
shouldRemoveAttribute,
statements: default,
handlesExpressions: default);
}
private static IMethodSymbol RemoveAttributesCore(
this IMethodSymbol method, Func shouldRemoveAttribute,
ImmutableArray statements, ImmutableArray handlesExpressions)
{
var methodHasAttribute = method.GetAttributes().Any(shouldRemoveAttribute);
var someParameterHasAttribute = method.Parameters
.Any(m => m.GetAttributes().Any(shouldRemoveAttribute));
var returnTypeHasAttribute = method.GetReturnTypeAttributes().Any(shouldRemoveAttribute);
if (!methodHasAttribute && !someParameterHasAttribute && !returnTypeHasAttribute)
{
return method;
}
return CodeGenerationSymbolFactory.CreateMethodSymbol(
method.ContainingType,
method.GetAttributes().WhereAsArray(a => !shouldRemoveAttribute(a)),
method.DeclaredAccessibility,
method.GetSymbolModifiers(),
method.ReturnType,
method.RefKind,
method.ExplicitInterfaceImplementations,
method.Name,
method.TypeParameters,
method.Parameters.SelectAsArray(p =>
CodeGenerationSymbolFactory.CreateParameterSymbol(
p.GetAttributes().WhereAsArray(a => !shouldRemoveAttribute(a)),
p.RefKind, p.IsParams, p.Type, p.Name, p.IsOptional,
p.HasExplicitDefaultValue, p.HasExplicitDefaultValue ? p.ExplicitDefaultValue : null)),
statements,
handlesExpressions,
method.GetReturnTypeAttributes().WhereAsArray(a => !shouldRemoveAttribute(a)));
}
public static bool? IsMoreSpecificThan(this IMethodSymbol method1, IMethodSymbol method2)
{
var p1 = method1.Parameters;
var p2 = method2.Parameters;
// If the methods don't have the same parameter count, then method1 can't be more or
// less specific than method2.
if (p1.Length != p2.Length)
{
return null;
}
// If the methods' parameter types differ, or they have different names, then one can't
// be more specific than the other.
if (!SignatureComparer.Instance.HaveSameSignature(method1.Parameters, method2.Parameters) ||
!method1.Parameters.Select(p => p.Name).SequenceEqual(method2.Parameters.Select(p => p.Name)))
{
return null;
}
// Ok. We have two methods that look extremely similar to each other. However, one might
// be more specific if, for example, it was actually written with concrete types (like 'int')
// versus the other which may have been instantiated from a type parameter. i.e.
//
// class C { void Goo(T t); void Goo(int t); }
//
// THe latter Goo is more specific when comparing "C.Goo(int t)" (method1) vs
// "C.Goo(int t)" (method2).
p1 = method1.OriginalDefinition.Parameters;
p2 = method2.OriginalDefinition.Parameters;
return p1.Select(p => p.Type).ToList().AreMoreSpecificThan(p2.Select(p => p.Type).ToList());
}
public static bool TryGetPredefinedComparisonOperator(this IMethodSymbol symbol, out PredefinedOperator op)
{
if (symbol.MethodKind == MethodKind.BuiltinOperator)
{
op = symbol.GetPredefinedOperator();
switch (op)
{
case PredefinedOperator.Equality:
case PredefinedOperator.Inequality:
case PredefinedOperator.GreaterThanOrEqual:
case PredefinedOperator.LessThanOrEqual:
case PredefinedOperator.GreaterThan:
case PredefinedOperator.LessThan:
return true;
}
}
else
{
op = PredefinedOperator.None;
}
return false;
}
public static PredefinedOperator GetPredefinedOperator(this IMethodSymbol symbol)
{
switch (symbol.Name)
{
case "op_Addition":
case "op_UnaryPlus":
return PredefinedOperator.Addition;
case "op_BitwiseAnd":
return PredefinedOperator.BitwiseAnd;
case "op_BitwiseOr":
return PredefinedOperator.BitwiseOr;
case "op_Concatenate":
return PredefinedOperator.Concatenate;
case "op_Decrement":
return PredefinedOperator.Decrement;
case "op_Division":
return PredefinedOperator.Division;
case "op_Equality":
return PredefinedOperator.Equality;
case "op_ExclusiveOr":
return PredefinedOperator.ExclusiveOr;
case "op_Exponent":
return PredefinedOperator.Exponent;
case "op_GreaterThan":
return PredefinedOperator.GreaterThan;
case "op_GreaterThanOrEqual":
return PredefinedOperator.GreaterThanOrEqual;
case "op_Increment":
return PredefinedOperator.Increment;
case "op_Inequality":
return PredefinedOperator.Inequality;
case "op_IntegerDivision":
return PredefinedOperator.IntegerDivision;
case "op_LeftShift":
return PredefinedOperator.LeftShift;
case "op_LessThan":
return PredefinedOperator.LessThan;
case "op_LessThanOrEqual":
return PredefinedOperator.LessThanOrEqual;
case "op_Like":
return PredefinedOperator.Like;
case "op_LogicalNot":
case "op_OnesComplement":
return PredefinedOperator.Complement;
case "op_Modulus":
return PredefinedOperator.Modulus;
case "op_Multiply":
return PredefinedOperator.Multiplication;
case "op_RightShift":
return PredefinedOperator.RightShift;
case "op_Subtraction":
case "op_UnaryNegation":
return PredefinedOperator.Subtraction;
default:
return PredefinedOperator.None;
}
}
///
/// Returns true for void returning methods with two parameters, where
/// the first parameter is of type and the second
/// parameter inherits from or equals type.
///
public static bool HasEventHandlerSignature(this IMethodSymbol method, INamedTypeSymbol eventArgsType)
=> eventArgsType != null &&
method.Parameters.Length == 2 &&
method.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
method.Parameters[1].Type.InheritsFromOrEquals(eventArgsType);
}
}