mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
14 KiB
360 lines
14 KiB
// 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) |
|
{ |
|
if (!context.Settings.UsingDeclarations) |
|
return; |
|
|
|
// 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 |
|
|
|
// 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; |
|
this.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 (Identifier ident in namespaceDeclaration.Identifiers) { |
|
currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident.Name); |
|
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; |
|
this.currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty; |
|
} |
|
|
|
public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) |
|
{ |
|
string oldNamespace = currentNamespace; |
|
foreach (Identifier ident in namespaceDeclaration.Identifiers) { |
|
currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident.Name); |
|
} |
|
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); |
|
} |
|
} |
|
} |
|
}
|
|
|