summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUsingDeclarations.cs
blob: 6e9cc4f57df4f91815743f1cc2ae6e7cc8db12b0 (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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
using Mono.Cecil;

namespace ICSharpCode.Decompiler.Ast.Transforms
{
	/// <summary>
	/// Introduces using declarations.
	/// </summary>
	public class IntroduceUsingDeclarations : IAstTransform
	{
		DecompilerContext context;
		
		public IntroduceUsingDeclarations(DecompilerContext context)
		{
			this.context = context;
		}
		
		public void Run(AstNode compilationUnit)
		{
			// First determine all the namespaces that need to be imported:
			compilationUnit.AcceptVisitor(new FindRequiredImports(this), null);
			
			importedNamespaces.Add("System"); // always import System, even when not necessary
			
			if (context.Settings.UsingDeclarations) {
				// Now add using declarations for those namespaces:
				foreach (string ns in importedNamespaces.OrderByDescending(n => n)) {
					// we go backwards (OrderByDescending) through the list of namespaces because we insert them backwards
					// (always inserting at the start of the list)
					string[] parts = ns.Split('.');
					AstType nsType = new SimpleType(parts[0]);
					for (int i = 1; i < parts.Length; i++) {
						nsType = new MemberType { Target = nsType, MemberName = parts[i] };
					}
					compilationUnit.InsertChildAfter(null, new UsingDeclaration { Import = nsType }, SyntaxTree.MemberRole);
				}
			}
			
			if (!context.Settings.FullyQualifyAmbiguousTypeNames)
				return;
			
			FindAmbiguousTypeNames(context.CurrentModule, internalsVisible: true);
			foreach (AssemblyNameReference r in context.CurrentModule.AssemblyReferences) {
				AssemblyDefinition d = context.CurrentModule.AssemblyResolver.Resolve(r);
				if (d != null)
					FindAmbiguousTypeNames(d.MainModule, internalsVisible: false);
			}
			
			// verify that the SimpleTypes refer to the correct type (no ambiguities)
			compilationUnit.AcceptVisitor(new FullyQualifyAmbiguousTypeNamesVisitor(this), null);
		}
		
		readonly HashSet<string> declaredNamespaces = new HashSet<string>() { string.Empty };
		readonly HashSet<string> importedNamespaces = new HashSet<string>();
		
		// Note that we store type names with `n suffix, so we automatically disambiguate based on number of type parameters.
		readonly HashSet<string> availableTypeNames = new HashSet<string>();
		readonly HashSet<string> ambiguousTypeNames = new HashSet<string>();
		
		sealed class FindRequiredImports : DepthFirstAstVisitor<object, object>
		{
			readonly IntroduceUsingDeclarations transform;
			string currentNamespace;
			
			public FindRequiredImports(IntroduceUsingDeclarations transform)
			{
				this.transform = transform;
				currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty;
			}
			
			bool IsParentOfCurrentNamespace(string ns)
			{
				if (ns.Length == 0)
					return true;
				if (currentNamespace.StartsWith(ns, StringComparison.Ordinal)) {
					if (currentNamespace.Length == ns.Length)
						return true;
					if (currentNamespace[ns.Length] == '.')
						return true;
				}
				return false;
			}
			
			public override object VisitSimpleType(SimpleType simpleType, object data)
			{
				TypeReference tr = simpleType.Annotation<TypeReference>();
				if (tr != null && !IsParentOfCurrentNamespace(tr.Namespace)) {
					transform.importedNamespaces.Add(tr.Namespace);
				}
				return base.VisitSimpleType(simpleType, data); // also visit type arguments
			}
			
			public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data)
			{
				string oldNamespace = currentNamespace;
				foreach (string ident in namespaceDeclaration.Identifiers) {
					currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident);
					transform.declaredNamespaces.Add(currentNamespace);
				}
				base.VisitNamespaceDeclaration(namespaceDeclaration, data);
				currentNamespace = oldNamespace;
				return null;
			}
		}
		
		void FindAmbiguousTypeNames(ModuleDefinition module, bool internalsVisible)
		{
			foreach (TypeDefinition type in module.Types) {
				if (internalsVisible || type.IsPublic) {
					if (importedNamespaces.Contains(type.Namespace) || declaredNamespaces.Contains(type.Namespace)) {
						if (!availableTypeNames.Add(type.Name))
							ambiguousTypeNames.Add(type.Name);
					}
				}
			}
		}
		
		sealed class FullyQualifyAmbiguousTypeNamesVisitor : DepthFirstAstVisitor<object, object>
		{
			readonly IntroduceUsingDeclarations transform;
			string currentNamespace;
			HashSet<string> currentMemberTypes;
			Dictionary<string, MemberReference> currentMembers;
			bool isWithinTypeReferenceExpression;
			
			public FullyQualifyAmbiguousTypeNamesVisitor(IntroduceUsingDeclarations transform)
			{
				this.transform = transform;
				currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty;
			}
			
			public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data)
			{
				string oldNamespace = currentNamespace;
				foreach (string ident in namespaceDeclaration.Identifiers) {
					currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident);
				}
				base.VisitNamespaceDeclaration(namespaceDeclaration, data);
				currentNamespace = oldNamespace;
				return null;
			}
			
			public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
			{
				HashSet<string> oldMemberTypes = currentMemberTypes;
				currentMemberTypes = currentMemberTypes != null ? new HashSet<string>(currentMemberTypes) : new HashSet<string>();
				
				Dictionary<string, MemberReference> oldMembers = currentMembers;
				currentMembers = new Dictionary<string, MemberReference>();
				
				TypeDefinition typeDef = typeDeclaration.Annotation<TypeDefinition>();
				bool privateMembersVisible = true;
				ModuleDefinition internalMembersVisibleInModule = typeDef.Module;
				while (typeDef != null) {
					foreach (GenericParameter gp in typeDef.GenericParameters) {
						currentMemberTypes.Add(gp.Name);
					}
					foreach (TypeDefinition t in typeDef.NestedTypes) {
						if (privateMembersVisible || IsVisible(t, internalMembersVisibleInModule))
							currentMemberTypes.Add(t.Name.Substring(t.Name.LastIndexOf('+') + 1));
					}
					
					foreach (MethodDefinition method in typeDef.Methods) {
						if (privateMembersVisible || IsVisible(method, internalMembersVisibleInModule))
							AddCurrentMember(method);
					}
					foreach (PropertyDefinition property in typeDef.Properties) {
						if (privateMembersVisible || IsVisible(property.GetMethod, internalMembersVisibleInModule) || IsVisible(property.SetMethod, internalMembersVisibleInModule))
							AddCurrentMember(property);
					}
					foreach (EventDefinition ev in typeDef.Events) {
						if (privateMembersVisible || IsVisible(ev.AddMethod, internalMembersVisibleInModule) || IsVisible(ev.RemoveMethod, internalMembersVisibleInModule))
							AddCurrentMember(ev);
					}
					foreach (FieldDefinition f in typeDef.Fields) {
						if (privateMembersVisible || IsVisible(f, internalMembersVisibleInModule))
							AddCurrentMember(f);
					}
					// repeat with base class:
					if (typeDef.BaseType != null)
						typeDef = typeDef.BaseType.Resolve();
					else
						typeDef = null;
					privateMembersVisible = false;
				}
				
				// Now add current members from outer classes:
				if (oldMembers != null) {
					foreach (var pair in oldMembers) {
						// add members from outer classes only if the inner class doesn't define the member
						if (!currentMembers.ContainsKey(pair.Key))
							currentMembers.Add(pair.Key, pair.Value);
					}
				}
				
				base.VisitTypeDeclaration(typeDeclaration, data);
				currentMembers = oldMembers;
				return null;
			}
			
			void AddCurrentMember(MemberReference m)
			{
				MemberReference existingMember;
				if (currentMembers.TryGetValue(m.Name, out existingMember)) {
					// We keep the existing member assignment if it was from another class (=from a derived class),
					// because members in derived classes have precedence over members in base classes.
					if (existingMember != null && existingMember.DeclaringType == m.DeclaringType) {
						// Use null as value to signalize multiple members with the same name
						currentMembers[m.Name] = null;
					}
				} else {
					currentMembers.Add(m.Name, m);
				}
			}
			
			bool IsVisible(MethodDefinition m, ModuleDefinition internalMembersVisibleInModule)
			{
				if (m == null)
					return false;
				switch (m.Attributes & MethodAttributes.MemberAccessMask) {
					case MethodAttributes.FamANDAssem:
					case MethodAttributes.Assembly:
						return m.Module == internalMembersVisibleInModule;
					case MethodAttributes.Family:
					case MethodAttributes.FamORAssem:
					case MethodAttributes.Public:
						return true;
					default:
						return false;
				}
			}
			
			bool IsVisible(FieldDefinition f, ModuleDefinition internalMembersVisibleInModule)
			{
				if (f == null)
					return false;
				switch (f.Attributes & FieldAttributes.FieldAccessMask) {
					case FieldAttributes.FamANDAssem:
					case FieldAttributes.Assembly:
						return f.Module == internalMembersVisibleInModule;
					case FieldAttributes.Family:
					case FieldAttributes.FamORAssem:
					case FieldAttributes.Public:
						return true;
					default:
						return false;
				}
			}
			
			bool IsVisible(TypeDefinition t, ModuleDefinition internalMembersVisibleInModule)
			{
				if (t == null)
					return false;
				switch (t.Attributes & TypeAttributes.VisibilityMask) {
					case TypeAttributes.NotPublic:
					case TypeAttributes.NestedAssembly:
					case TypeAttributes.NestedFamANDAssem:
						return t.Module == internalMembersVisibleInModule;
					case TypeAttributes.NestedFamily:
					case TypeAttributes.NestedFamORAssem:
					case TypeAttributes.NestedPublic:
					case TypeAttributes.Public:
						return true;
					default:
						return false;
				}
			}
			
			public override object VisitSimpleType(SimpleType simpleType, object data)
			{
				// Handle type arguments first, so that the fixed-up type arguments get moved over to the MemberType,
				// if we're also creating one here.
				base.VisitSimpleType(simpleType, data);
				TypeReference tr = simpleType.Annotation<TypeReference>();
				// Fully qualify any ambiguous type names.
				if (tr != null && IsAmbiguous(tr.Namespace, tr.Name)) {
					AstType ns;
					if (string.IsNullOrEmpty(tr.Namespace)) {
						ns = new SimpleType("global");
					} else {
						string[] parts = tr.Namespace.Split('.');
						if (IsAmbiguous(string.Empty, parts[0])) {
							// conflict between namespace and type name/member name
							ns = new MemberType { Target = new SimpleType("global"), IsDoubleColon = true, MemberName = parts[0] };
						} else {
							ns = new SimpleType(parts[0]);
						}
						for (int i = 1; i < parts.Length; i++) {
							ns = new MemberType { Target = ns, MemberName = parts[i] };
						}
					}
					MemberType mt = new MemberType();
					mt.Target = ns;
					mt.IsDoubleColon = string.IsNullOrEmpty(tr.Namespace);
					mt.MemberName = simpleType.Identifier;
					mt.CopyAnnotationsFrom(simpleType);
					simpleType.TypeArguments.MoveTo(mt.TypeArguments);
					simpleType.ReplaceWith(mt);
				}
				return null;
			}
			
			public override object VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, object data)
			{
				isWithinTypeReferenceExpression = true;
				base.VisitTypeReferenceExpression(typeReferenceExpression, data);
				isWithinTypeReferenceExpression = false;
				return null;
			}
			
			bool IsAmbiguous(string ns, string name)
			{
				// If the type name conflicts with an inner class/type parameter, we need to fully-qualify it:
				if (currentMemberTypes != null && currentMemberTypes.Contains(name))
					return true;
				// If the type name conflicts with a field/property etc. on the current class, we need to fully-qualify it,
				// if we're inside an expression.
				if (isWithinTypeReferenceExpression && currentMembers != null) {
					MemberReference mr;
					if (currentMembers.TryGetValue(name, out mr)) {
						// However, in the special case where the member is a field or property with the same type
						// as is requested, then we can use the short name (if it's not otherwise ambiguous)
						PropertyDefinition prop = mr as PropertyDefinition;
						FieldDefinition field = mr as FieldDefinition;
						if (!(prop != null && prop.PropertyType.Namespace == ns && prop.PropertyType.Name == name)
						    && !(field != null && field.FieldType.Namespace == ns && field.FieldType.Name == name))
							return true;
					}
				}
				// If the type is defined in the current namespace,
				// then we can use the short name even if we imported type with same name from another namespace.
				if (ns == currentNamespace && !string.IsNullOrEmpty(ns))
					return false;
				return transform.ambiguousTypeNames.Contains(name);
			}
		}
	}
}