summaryrefslogtreecommitdiffstats
path: root/tools/Sandcastle/Source/MRefBuilder/ExtensionMethodAddIn.cs
blob: f935921d7ac2bf263d8ccb62173bfd43adbf472d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// Copyright © Microsoft Corporation.
// This source file is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.XPath;

using System.Compiler;

using Microsoft.Ddue.Tools.Reflection;

namespace Microsoft.Ddue.Tools {

    // Extension method add in

    public class ExtensionMethodAddIn : MRefBuilderAddIn {

        private Dictionary < TypeNode, List < Method > > index = new Dictionary < TypeNode, List < Method > >();

        private bool isExtensionMethod = false;

        private ManagedReflectionWriter reflector;

        public ExtensionMethodAddIn(ManagedReflectionWriter reflector, XPathNavigator configuration) : base(reflector, configuration) {
            this.reflector = reflector;
            reflector.RegisterStartTagCallback("apis", new MRefBuilderCallback(RecordExtensionMethods));
            reflector.RegisterEndTagCallback("elements", new MRefBuilderCallback(AddExtensionMethods));
            reflector.RegisterStartTagCallback("apidata", new MRefBuilderCallback(AddExtensionSubsubgroup));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="type">the current type being extended</param>
        /// <param name="extensionMethodTemplate">A reference to the extension method. For generic methods, this is a reference to the 
        /// non-specialized method, e.g. System.Linq.Enumerable.Select``2.
        /// </param>
        /// <param name="specialization">When the current type implements or inherits from a specialization of a generic type,
        /// this parameter has a TypeNode for the type used as apecialization of the generic type's first template param. 
        /// </param>
        private void AddExtensionMethod(XmlWriter writer, TypeNode type, Method extensionMethodTemplate, TypeNode specialization)
        {
            // If this is a specialization of a generic method, construct a Method object that describes the specialization
            Method extensionMethodTemplate2 = extensionMethodTemplate;
            if (extensionMethodTemplate2.IsGeneric && (specialization != null))
            {
                // the specialization type is the first of the method's template arguments
                TypeNodeList templateArgs = new TypeNodeList();
                templateArgs.Add(specialization);
                // add any additional template arguments
                for (int i = 1; i < extensionMethodTemplate.TemplateParameters.Count; i++)
                    templateArgs.Add(extensionMethodTemplate.TemplateParameters[i]);
                extensionMethodTemplate2 = extensionMethodTemplate.GetTemplateInstance(type, templateArgs);
            }
            TypeNode extensionMethodTemplateReturnType = extensionMethodTemplate2.ReturnType;
            ParameterList extensionMethodTemplateParameters = extensionMethodTemplate2.Parameters;

            ParameterList extensionMethodParameters = new ParameterList();
            for (int i = 1; i < extensionMethodTemplateParameters.Count; i++)
            {
                Parameter extensionMethodParameter = extensionMethodTemplateParameters[i];
                extensionMethodParameters.Add(extensionMethodParameter);
            }
            Method extensionMethod = new Method(extensionMethodTemplate.DeclaringType, new AttributeList(), extensionMethodTemplate.Name, extensionMethodParameters, extensionMethodTemplate.ReturnType, null);
            extensionMethod.Flags = extensionMethodTemplate.Flags & ~MethodFlags.Static;

            // for generic methods, set the template args and params so the template data is included in the id and the method data
            if (extensionMethodTemplate2.IsGeneric)
            {
                extensionMethod.IsGeneric = true;
                if (specialization != null)
                {
                    // set the template args for the specialized generic method
                    extensionMethod.TemplateArguments = extensionMethodTemplate2.TemplateArguments;
                }
                else
                {
                    // set the generic template params for the non-specialized generic method
                    extensionMethod.TemplateParameters = extensionMethodTemplate2.TemplateParameters;
                }
            }

            // Get the id
            string extensionMethodTemplateId = reflector.ApiNamer.GetMemberName(extensionMethodTemplate);

            // write the element node
            writer.WriteStartElement("element");
            writer.WriteAttributeString("api", extensionMethodTemplateId);
            writer.WriteAttributeString("source", "extension");
            isExtensionMethod = true;
            reflector.WriteMember(extensionMethod);
            isExtensionMethod = false;
            writer.WriteEndElement();
        }

        private void AddExtensionMethods(XmlWriter writer, Object info)
        {

            MemberDictionary members = info as MemberDictionary;
            if (members == null) return;

            TypeNode type = members.Type;

            InterfaceList contracts = type.Interfaces;
            foreach (Interface contract in contracts)
            {
                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)ReflectionUtilities.GetTemplateType(contract);
                    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 = ReflectionUtilities.GetTemplateType(comparisonType);
                    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;
            }
        }

        private void AddExtensionSubsubgroup(XmlWriter writer, Object data) {
            if (isExtensionMethod) writer.WriteAttributeString("subsubgroup", "extension");
        }

        private bool HasExtensionAttribute(Method method) {
            AttributeList attributes = method.Attributes;
            foreach (AttributeNode attribute in attributes) {
                if (attribute.Type.FullName == "System.Runtime.CompilerServices.ExtensionAttribute") return (true);
            }
            return (false);
        }

        private bool IsValidTemplateArgument(TypeNode type, TypeNode parameter) {
            if (type == null) throw new ArgumentNullException("type");
            if (parameter == null) throw new ArgumentNullException("parameter");

            // check that the parameter really is a type parameter

            ITypeParameter itp = parameter as ITypeParameter;
            if (itp == null) throw new ArgumentException("The 'parameter' argument is null or not an 'ITypeParameter'.");

            // test constraints

            bool reference = ((itp.TypeParameterFlags & TypeParameterFlags.ReferenceTypeConstraint) > 0);
            if (reference && type.IsValueType) return (false);

            bool value = ((itp.TypeParameterFlags & TypeParameterFlags.ValueTypeConstraint) > 0);
            if (value && !type.IsValueType) return (false);

            bool constructor = ((itp.TypeParameterFlags & TypeParameterFlags.DefaultConstructorConstraint) > 0);


            InterfaceList contracts = parameter.Interfaces;
            if (contracts != null) {
                foreach (Interface contract in contracts) {
                    if (!type.IsAssignableTo(contract)) return (false);
                }
            }

            TypeNode parent = parameter.BaseType;
            if ((parent != null) && !type.IsAssignableTo(parent)) return (false);

            // okay, passed all tests

            return (true);

        }

        private void RecordExtensionMethods(XmlWriter writer, Object info)
        {
            NamespaceList spaces = (NamespaceList)info;
            foreach (Namespace space in spaces)
            {
                TypeNodeList types = space.Types;
                foreach (TypeNode type in types)
                {

                    MemberList members = type.Members;

                    // go through the members, looking for fields signaling extension methods
                    foreach (Member member in members)
                    {

                        Method method = member as Method;
                        if (method == null) continue;

                        if (!reflector.ApiFilter.IsExposedMember(method)) continue;

                        if (!HasExtensionAttribute(method)) continue;

                        ParameterList parameters = method.Parameters;
                        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 arg 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 param.
                        if (method.IsGeneric && (method.TemplateParameters.Count > 0))
                        {
                            if (extendedType.IsGeneric && (extendedType.TemplateArguments != null) && (extendedType.TemplateArguments.Count == 1))
                            {
                                // is the extended type's template arg 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 = ReflectionUtilities.GetTemplateType(extendedType);
                                    }
                                }
                            }
                        }

                        List<Method> methods = null;
                        if (!index.TryGetValue(extendedType, out methods))
                        {
                            methods = new List<Method>();
                            index.Add(extendedType, methods);
                        }
                        methods.Add(method);

                    }
                }

            }
        }

        /// <summary>
        /// Determines whether an extension method is hidden by a member that's already defined on the type being extended.
        /// The extension method is hidden if it has the same name, template params, and parameters as a defined method.
        /// </summary>
        /// <param name="extensionMethod">The extension method to compare.</param>
        /// <param name="members">A dictionary of the members defined on the type being extended.</param>
        /// <returns></returns>
        private bool IsExtensionMethodHidden(Method extensionMethod, MemberDictionary members)
        {
            if (!members.MemberNames.Contains(extensionMethod.Name.Name))
                return false;

            // get a list of members with the same name as the extension method
            List<Member> membersList = members[extensionMethod.Name.Name];
            foreach (Member member in membersList)
            {
                // the hiding member must be a method
                if (member.NodeType != NodeType.Method)
                    continue;
                Method method = (Method)member;

                // do the generic template parameters of both methods match?
                if (!method.TemplateParametersMatch(extensionMethod.TemplateParameters))
                    continue;

                // do both methods have the same number of parameters?
                // (not counting the extension method's first param, which identifies the extended type)
                if (method.Parameters.Count != (extensionMethod.Parameters.Count - 1))
                    continue;

                // do the parameter types of both methods match?
                if (DoParameterTypesMatch(extensionMethod.Parameters, method.Parameters))
                    return true;
            }
            return false;
        }

        private bool DoParameterTypesMatch(ParameterList extensionParams, ParameterList methodParams)
        {
            for (int i = 0; i < methodParams.Count; i++)
            {
                if (methodParams[i].Type.FullName != extensionParams[i + 1].Type.FullName)
                    return false;
            }
            return true;
        }



    }

}