|
|
|
@ -12,14 +12,13 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
@@ -12,14 +12,13 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Introduces using declarations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class IntroduceUsingDeclarations : DepthFirstAstVisitor<object, object>, IAstTransform |
|
|
|
|
public class IntroduceUsingDeclarations : IAstTransform |
|
|
|
|
{ |
|
|
|
|
DecompilerContext context; |
|
|
|
|
|
|
|
|
|
public IntroduceUsingDeclarations(DecompilerContext context) |
|
|
|
|
{ |
|
|
|
|
this.context = context; |
|
|
|
|
currentNamespace = context.CurrentType != null ? context.CurrentType.Namespace : string.Empty; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public void Run(AstNode compilationUnit) |
|
|
|
@ -28,7 +27,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
@@ -28,7 +27,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
// First determine all the namespaces that need to be imported:
|
|
|
|
|
compilationUnit.AcceptVisitor(this, null); |
|
|
|
|
compilationUnit.AcceptVisitor(new FindRequiredImports(this), null); |
|
|
|
|
|
|
|
|
|
importedNamespaces.Add("System"); // always import System, even when not necessary
|
|
|
|
|
|
|
|
|
@ -55,48 +54,60 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
@@ -55,48 +54,60 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// verify that the SimpleTypes refer to the correct type (no ambiguities)
|
|
|
|
|
FullyQualifyAmbiguousTypeNames(compilationUnit); |
|
|
|
|
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>(); |
|
|
|
|
string currentNamespace; |
|
|
|
|
|
|
|
|
|
bool IsParentOfCurrentNamespace(string ns) |
|
|
|
|
sealed class FindRequiredImports : DepthFirstAstVisitor<object, object> |
|
|
|
|
{ |
|
|
|
|
if (ns.Length == 0) |
|
|
|
|
return true; |
|
|
|
|
if (currentNamespace.StartsWith(ns, StringComparison.Ordinal)) { |
|
|
|
|
if (currentNamespace.Length == ns.Length) |
|
|
|
|
return true; |
|
|
|
|
if (currentNamespace[ns.Length] == '.') |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override object VisitSimpleType(SimpleType simpleType, object data) |
|
|
|
|
{ |
|
|
|
|
TypeReference tr = simpleType.Annotation<TypeReference>(); |
|
|
|
|
if (tr != null && !IsParentOfCurrentNamespace(tr.Namespace)) { |
|
|
|
|
importedNamespaces.Add(tr.Namespace); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
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); |
|
|
|
|
declaredNamespaces.Add(currentNamespace); |
|
|
|
|
} |
|
|
|
|
base.VisitNamespaceDeclaration(namespaceDeclaration, data); |
|
|
|
|
currentNamespace = oldNamespace; |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void FindAmbiguousTypeNames(ModuleDefinition module, bool internalsVisible) |
|
|
|
@ -111,17 +122,176 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
@@ -111,17 +122,176 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void FullyQualifyAmbiguousTypeNames(AstNode compilationUnit) |
|
|
|
|
sealed class FullyQualifyAmbiguousTypeNamesVisitor : DepthFirstAstVisitor<object, object> |
|
|
|
|
{ |
|
|
|
|
foreach (SimpleType simpleType in compilationUnit.Descendants.OfType<SimpleType>()) { |
|
|
|
|
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>(); |
|
|
|
|
if (tr != null && ambiguousTypeNames.Contains(tr.Name)) { |
|
|
|
|
// 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('.'); |
|
|
|
|
ns = new SimpleType(parts[0]); |
|
|
|
|
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] }; |
|
|
|
|
} |
|
|
|
@ -134,6 +304,41 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
@@ -134,6 +304,41 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
|
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|