/// <summary> /// See if the given sets of parameters match each other /// </summary> /// <param name="parameters1">The first set of parameters</param> /// <param name="parameters2">The second set of parameters</param> /// <param name="noMatchOnGenericVersions">True to fail the match if either parameter is /// generic, false to allow matching to generic parameters even if the other isn't.</param> /// <returns>True if they match, false if not</returns> /// <param name="allowMismatchedArrayTypes">True to allow a match with mismatched array types or false to /// not allow a match. If true, we're getting pretty desperate for a match.</param> /// <remarks>When <paramref name="noMatchOnGenericVersions"/> is true, it prevents matching a /// non-generic overload of the method to a generic version of the method. This allows the /// non-generic version to be matched correctly (i.e. Contains(T) and Contains(Guid)). If not /// done, the generic version is matched to both methods and the reflection info contains a /// duplicate generic method and loses the non-generic overload.</remarks> private static bool ParametersMatch(ParameterList parameters1, ParameterList parameters2, bool noMatchOnGenericVersions, bool allowMismatchedArrayTypes) { if (parameters1.Count != parameters2.Count) { return(false); } for (int i = 0; i < parameters1.Count; i++) { TypeNode type1 = parameters1[i].Type; TypeNode type2 = parameters2[i].Type; // !EFW - Fail the match if we are looking for a non-generic match if (noMatchOnGenericVersions && (type1.IsTemplateParameter || type2.IsTemplateParameter)) { return(false); } // We can't determine the equivalence of template parameters; this is probably not good if (type1.IsTemplateParameter || type2.IsTemplateParameter) { // !EFW - As a fallback, compare the type parameter positions. If they don't match, this // probably isn't the one we want. int p1 = GetTemplateParameterPosition(parameters1[i].DeclaringMethod.DeclaringType, type1.Name.Name), p2 = GetTemplateParameterPosition(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); if (p1 != -1 && p2 != -1 && p1 != p2) { // !EFW - Another test case supplied by Jared Moore. If the types are something like // MyBaseClass<T, T> and MyBaseClass<T, U> we can still provide a match by comparing // all possible positions. As long as they intersect, it's probably a good match. var positions1 = GetTemplateParameterPositions(parameters1[i].DeclaringMethod.DeclaringType, type1.Name.Name); var positions2 = GetTemplateParameterPositions(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); // If we found any but none of them are the same, then no match. if (positions1.Any() && positions2.Any() && !positions1.Intersect(positions2).Any()) { return(false); } } } else { // The node type must be the same; this is probably a fast check if (type1.NodeType != type2.NodeType) { return(false); } // If they are "normal" types, we will compare them. Comparing arrays, pointers, etc. is // dangerous, because the types they contain may be template parameters if (type1.NodeType == NodeType.Class || type1.NodeType == NodeType.Struct || type1.NodeType == NodeType.Interface || type1.NodeType == NodeType.EnumNode || type1.NodeType == NodeType.DelegateNode) { type1 = type1.GetTemplateType(); type2 = type2.GetTemplateType(); if (!type2.IsStructurallyEquivalentTo(type1)) { return(false); } } // !EFW - Comparing array types may be dangerous but, as it turns out, is necessary. If // two overloads take an array as a parameter, it always returns the first overload as the // match in derived types. As such, we do need to compare the array element types. For // generic types, we can get the underlying template type from the declaring method's type // and match that. // https://github.com/EWSoftware/SHFB/issues/57 if (type1.NodeType == NodeType.ArrayType) { type1 = ((ArrayType)type1).ElementType; type2 = ((ArrayType)type2).ElementType; if (type2.IsTemplateParameter) { // Get the position from the second set of parameters int pos = GetTemplateParameterPosition(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); // Get the actual type from the first set of parameters var declType = parameters1[i].DeclaringMethod.DeclaringType; if (pos != -1 && declType.TemplateArguments != null && pos < declType.TemplateArguments.Count) { type2 = declType.TemplateArguments[pos]; } } if (type1.NodeType != type2.NodeType || !type2.IsStructurallyEquivalentTo(type1)) { // !EFW - Yet another edge case to check. In this case for example, // KeyValue<int, int> didn't match KeyValue<TKey, TValue> and it failed to find any // matches. The fix is to see if both types are generic and compare the template // parameter names. This is getting rather complicated isn't it? // https://github.com/EWSoftware/SHFB/issues/154 if (!type1.IsGeneric || !type2.IsGeneric || type1.Template == null || type2.Template == null || type1.Template.TemplateParameters.Count != type2.Template.TemplateParameters.Count || type1.Template.TemplateParameters.Select(t => t.Name.Name).Except( type2.Template.TemplateParameters.Select(t => t.Name.Name)).Count() != 0) { // If this is the last ditch attempt and were allowing mismatched array types, // we're pretty much screwed so carry on. This can happen in some really // complex cases were we end up with an intrinsic type and a template parameter: // https://github.com/EWSoftware/SHFB/issues/302 if (!allowMismatchedArrayTypes || type1.StructuralElementTypes == null || type2.StructuralElementTypes == null || type1.StructuralElementTypes.Count == 0 || type2.StructuralElementTypes.Count == 0 || type1.StructuralElementTypes[0].IsTemplateParameter == type2.StructuralElementTypes[0].IsTemplateParameter) { return(false); } } } } } } return(true); }
//===================================================================== /// <summary> /// This finds all extension methods, adds information about them to the types, and tracks /// them for adding to the reflection data later in the other callbacks. /// </summary> /// <param name="writer">The reflection data XML writer</param> /// <param name="info">For this callback, the information object is a namespace list</param> private void RecordExtensionMethods(XmlWriter writer, object info) { NamespaceList spaces = (NamespaceList)info; foreach (Namespace space in spaces) { // !EFW - Don't bother checking unexposed namespaces if (!mrw.ApiFilter.IsExposedNamespace(space)) { continue; } TypeNodeList types = space.Types; foreach (TypeNode type in types) { // !EFW - Don't bother checking unexposed types if (!mrw.ApiFilter.IsExposedType(type)) { continue; } // Go through the members looking for fields signaling extension methods. Members may be // added so convert to a list first to avoid enumeration issues. foreach (Member member in type.Members.ToList()) { Method method = member as Method; if (method == null || !mrw.ApiFilter.IsExposedMember(method) || !method.Attributes.Any(a => a.Type.FullName == "System.Runtime.CompilerServices.ExtensionAttribute")) { continue; } ParameterList parameters = method.Parameters; // !EFW - This fix was reported without an example. Sometimes, there are no parameters. // In such cases, ignore it to prevent a crash. if (parameters == null || parameters.Count == 0) { continue; } TypeNode extendedType = parameters[0].Type; // Recognize generic extension methods where the extended type is a specialization of a // generic type and the extended type's specialized template argument is a type parameter // declared by the generic extension method. In this case, we need to save a TypeNode // for the non-specialized type in the index because a TypeNode for the specialized type // won't match correctly in AddExtensionMethods(). NOTE: we are not interested in // extended types that are specialized by a specific type rather than by the extension // method's template parameter. if (method.IsGeneric && method.TemplateParameters.Count > 0) { if (extendedType.IsGeneric && extendedType.TemplateArguments != null && extendedType.TemplateArguments.Count == 1) { // Is the extended type's template argument a template parameter rather than a // specialized type? TypeNode arg = extendedType.TemplateArguments[0]; if (arg.IsTemplateParameter) { // Is the template parameter declared on the extension method ITypeParameter gtp = (ITypeParameter)arg; if (gtp.DeclaringMember == method && gtp.ParameterListIndex == 0) { // Get a TypeNode for the non-specialized type extendedType = extendedType.GetTemplateType(); } } } } List <Method> methods = null; if (!index.TryGetValue(extendedType, out methods)) { methods = new List <Method>(); index.Add(extendedType, methods); } methods.Add(method); } } } }
/// <summary> /// See if the given sets of parameters match each other /// </summary> /// <param name="parameters1">The first set of parameters</param> /// <param name="parameters2">The second set of parameters</param> /// <param name="noMatchOnGenericVersions">True to fail the match if either parameter is /// generic, false to allow matching to generic parameters even if the other isn't.</param> /// <returns>True if they match, false if not</returns> /// <remarks>When <paramref name="noMatchOnGenericVersions"/> is true, it prevents matching a /// non-generic overload of the method to a generic version of the method. This allows the /// non-generic version to be matched correctly (i.e. Contains(T) and Contains(Guid)). If not /// done, the generic version is matched to both methods and the reflection info contains a /// duplicate generic method and loses the non-generic overload.</remarks> private static bool ParametersMatch(ParameterList parameters1, ParameterList parameters2, bool noMatchOnGenericVersions) { if (parameters1.Count != parameters2.Count) { return(false); } for (int i = 0; i < parameters1.Count; i++) { TypeNode type1 = parameters1[i].Type; TypeNode type2 = parameters2[i].Type; // !EFW - Fail the match if we are looking for a non-generic match if (noMatchOnGenericVersions && (type1.IsTemplateParameter || type2.IsTemplateParameter)) { return(false); } // We can't determine the equivalence of template parameters; this is probably not good if (type1.IsTemplateParameter || type2.IsTemplateParameter) { // !EFW - As a fallback, compare the type parameter positions. If they don't match, this // probably isn't the one we want. int p1 = GetTemplateParameterPosition(parameters1[i].DeclaringMethod.DeclaringType, type1.Name.Name), p2 = GetTemplateParameterPosition(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); if (p1 != -1 && p2 != -1 && p1 != p2) { // !EFW - Another test case supplied by Jared Moore. If the types are something like // MyBaseClass<T, T> and MyBaseClass<T, U> we can still provide a match by comparing // all possible positions. As long as they intersect, it's probably a good match. var positions1 = GetTemplateParameterPositions(parameters1[i].DeclaringMethod.DeclaringType, type1.Name.Name); var positions2 = GetTemplateParameterPositions(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); // If we found any but none of them are the same, then no match. if (positions1.Any() && positions2.Any() && !positions1.Intersect(positions2).Any()) { return(false); } } } else { // The node type must be the same; this is probably a fast check if (type1.NodeType != type2.NodeType) { return(false); } // If they are "normal" types, we will compare them. Comparing arrays, pointers, etc. is // dangerous, because the types they contain may be template parameters if (type1.NodeType == NodeType.Class || type1.NodeType == NodeType.Struct || type1.NodeType == NodeType.Interface || type1.NodeType == NodeType.EnumNode || type1.NodeType == NodeType.DelegateNode) { type1 = type1.GetTemplateType(); type2 = type2.GetTemplateType(); if (!type2.IsStructurallyEquivalentTo(type1)) { return(false); } } // !EFW - Comparing array types may be dangerous but, as it turns out, is necessary. If // two overloads take an array as a parameter, it always returns the first overload as the // match in derived types. As such, we do need to compare the array element types. For // generic types, we can get the underlying template type from the declaring method's type // and match that. // https://github.com/EWSoftware/SHFB/issues/57 if (type1.NodeType == NodeType.ArrayType) { type1 = ((ArrayType)type1).ElementType; type2 = ((ArrayType)type2).ElementType; if (type2.IsTemplateParameter) { // Get the position from the second set of parameters int pos = GetTemplateParameterPosition(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); // Get the actual type from the first set of parameters var declType = parameters1[i].DeclaringMethod.DeclaringType; if (pos != -1 && declType.TemplateArguments != null && pos < declType.TemplateArguments.Count) { type2 = declType.TemplateArguments[pos]; } } if (type1.NodeType != type2.NodeType || !type2.IsStructurallyEquivalentTo(type1)) { return(false); } } } } return(true); }
/// <summary> /// This is used to add the extension method elements to the type's member list /// </summary> /// <param name="writer">The reflection data XML writer</param> /// <param name="info">For this callback, this is a member dictionary</param> private void AddExtensionMethods(XmlWriter writer, object info) { MemberDictionary members = info as MemberDictionary; if (members == null) { return; } TypeNode type = members.Type; foreach (Interface contract in type.Interfaces) { List <Method> extensionMethods = null; if (index.TryGetValue(contract, out extensionMethods)) { foreach (Method extensionMethod in extensionMethods) { if (!IsExtensionMethodHidden(extensionMethod, members)) { AddExtensionMethod(writer, type, extensionMethod, null); } } } if (contract.IsGeneric && contract.TemplateArguments != null && contract.TemplateArguments.Count > 0) { Interface templateContract = (Interface)contract.GetTemplateType(); TypeNode specialization = contract.TemplateArguments[0]; if (index.TryGetValue(templateContract, out extensionMethods)) { foreach (Method extensionMethod in extensionMethods) { if (IsValidTemplateArgument(specialization, extensionMethod.TemplateParameters[0])) { if (!IsExtensionMethodHidden(extensionMethod, members)) { AddExtensionMethod(writer, type, extensionMethod, specialization); } } } } } } TypeNode comparisonType = type; while (comparisonType != null) { List <Method> extensionMethods = null; if (index.TryGetValue(comparisonType, out extensionMethods)) { foreach (Method extensionMethod in extensionMethods) { if (!IsExtensionMethodHidden(extensionMethod, members)) { AddExtensionMethod(writer, type, extensionMethod, null); } } } if (comparisonType.IsGeneric && comparisonType.TemplateArguments != null && comparisonType.TemplateArguments.Count > 0) { TypeNode templateType = comparisonType.GetTemplateType(); TypeNode specialization = comparisonType.TemplateArguments[0]; if (index.TryGetValue(templateType, out extensionMethods)) { foreach (Method extensionMethod in extensionMethods) { if (IsValidTemplateArgument(specialization, extensionMethod.TemplateParameters[0])) { if (!IsExtensionMethodHidden(extensionMethod, members)) { AddExtensionMethod(writer, type, extensionMethod, specialization); } } } } } comparisonType = comparisonType.BaseType; } }
/// <summary> /// See if the given sets of parameters match each other /// </summary> /// <param name="parameters1">The first set of parameters</param> /// <param name="parameters2">The second set of parameters</param> /// <param name="noMatchOnGenericVersions">True to fail the match if either parameter is /// generic, false to allow matching to generic parameters even if the other isn't.</param> /// <returns>True if they match, false if not</returns> /// <remarks>When <paramref name="noMatchOnGenericVersions"/> is true, it prevents matching a /// non-generic overload of the method to a generic version of the method. This allows the /// non-generic version to be matched correctly (i.e. Contains(T) and Contains(Guid)). If not /// done, the generic version is matched to both methods and the reflection info contains a /// duplicate generic method and loses the non-generic overload.</remarks> private static bool ParametersMatch(ParameterList parameters1, ParameterList parameters2, bool noMatchOnGenericVersions) { if (parameters1.Count != parameters2.Count) { return(false); } for (int i = 0; i < parameters1.Count; i++) { TypeNode type1 = parameters1[i].Type; TypeNode type2 = parameters2[i].Type; // !EFW - Fail the match if we are looking for a non-generic match if (noMatchOnGenericVersions && (type1.IsTemplateParameter || type2.IsTemplateParameter)) { return(false); } // We can't determine the equivalence of template parameters; this is probably not good if (type1.IsTemplateParameter || type2.IsTemplateParameter) { // !EFW - As a fallback, compare the type parameter positions. If they don't match, this // probably isn't the one we want. int p1 = GetTemplateParameterPosition(parameters1[i].DeclaringMethod.DeclaringType, type1.Name.Name), p2 = GetTemplateParameterPosition(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); if (p1 != -1 && p2 != -1 && p1 != p2) { // !EFW - Another test case supplied by Jared Moore. If the types are something like // MyBaseClass<T, T> and MyBaseClass<T, U> we can still provide a match by comparing // all possible positions. As long as they intersect, it's probably a good match. var positions1 = GetTemplateParameterPositions(parameters1[i].DeclaringMethod.DeclaringType, type1.Name.Name); var positions2 = GetTemplateParameterPositions(parameters2[i].DeclaringMethod.DeclaringType, type2.Name.Name); // If we found any but none of them are the same, then no match. if (positions1.Any() && positions2.Any() && !positions1.Intersect(positions2).Any()) { return(false); } } } else { // The node type must be the same; this is probably a fast check if (type1.NodeType != type2.NodeType) { return(false); } // If they are "normal" types, we will compare them. Comparing arrays, pointers, etc. is // dangerous, because the types they contain may be template parameters if (type1.NodeType == NodeType.Class || type1.NodeType == NodeType.Struct || type1.NodeType == NodeType.Interface || type1.NodeType == NodeType.EnumNode || type1.NodeType == NodeType.DelegateNode) { type1 = type1.GetTemplateType(); type2 = type2.GetTemplateType(); if (!type2.IsStructurallyEquivalentTo(type1)) { return(false); } } } } return(true); }