Output: Handle scoped type name resolution conflicts (CS0104)

This commit is contained in:
Katy Coe
2019-11-17 22:27:44 +01:00
parent e2f423b7f2
commit 32608874cc
9 changed files with 217 additions and 72 deletions

View File

@@ -22,7 +22,7 @@ namespace Il2CppInspector.Reflection
public override string ToString() => DeclaringType.Name + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")";
public override string GetSignatureString() => Name + GetTypeParametersString()
public override string GetSignatureString(Scope usingScope) => Name + GetTypeParametersString(usingScope)
+ "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")";
}
}

View File

@@ -61,7 +61,7 @@ namespace Il2CppInspector.Reflection
public static IList<CustomAttributeData> GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.Definition.token, evt.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.Definition.token, field.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.Definition.token, method.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.Member.Assembly, param.Definition.token, param.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.Definition.token, param.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(PropertyInfo prop) => getCustomAttributes(prop.Assembly, prop.Definition.token, prop.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(TypeInfo type) => getCustomAttributes(type.Assembly, type.Definition.token, type.Definition.customAttributeIndex);
}

View File

@@ -1,4 +1,10 @@
using System;
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -8,8 +14,8 @@ namespace Il2CppInspector.Reflection
public static class Extensions
{
// Convert a list of CustomAttributeData objects into C#-friendly attribute usages
public static string ToString(this IEnumerable<CustomAttributeData> attributes, string linePrefix = "", string attributePrefix = "",
bool inline = false, bool emitPointer = false, bool mustCompile = false) {
public static string ToString(this IEnumerable<CustomAttributeData> attributes, Scope scope = null,
string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) {
var sb = new StringBuilder();
foreach (var cad in attributes) {
@@ -20,7 +26,7 @@ namespace Il2CppInspector.Reflection
var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
var name = cad.AttributeType.CSharpName;
var name = cad.AttributeType.GetScopedCSharpName(scope);
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
if (suffix != -1)
name = name[..suffix];

View File

@@ -128,7 +128,7 @@ namespace Il2CppInspector.Reflection
_ => ""
};
public string GetModifierString() {
public string GetModifierString(Scope usingScope) {
// Interface methods and properties have no visible modifiers (they are always declared 'public abstract')
if (DeclaringType.IsInterface)
return string.Empty;
@@ -151,7 +151,7 @@ namespace Il2CppInspector.Reflection
modifiers.Append("extern ");
// Method hiding
if ((DeclaringType.BaseType?.GetAllMethods().Any(m => m.GetSignatureString() == GetSignatureString() && m.IsHideBySig) ?? false)
if ((DeclaringType.BaseType?.GetAllMethods().Any(m => m.GetSignatureString(usingScope) == GetSignatureString(usingScope) && m.IsHideBySig) ?? false)
&& (((Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot && !IsVirtual)
|| (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot))
modifiers.Append($"new ");
@@ -166,13 +166,13 @@ namespace Il2CppInspector.Reflection
}
// Get C# syntax-friendly list of parameters
public string GetParametersString(bool emitPointer = false, bool commentAttributes = false)
=> string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(emitPointer, commentAttributes)));
public string GetParametersString(Scope usingScope, bool emitPointer = false, bool commentAttributes = false)
=> string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(usingScope, emitPointer, commentAttributes)));
public string GetTypeParametersString() => GenericTypeParameters == null? "" :
"<" + string.Join(", ", GenericTypeParameters.Select(p => p.CSharpName)) + ">";
public string GetTypeParametersString(Scope usingScope) => GenericTypeParameters == null? "" :
"<" + string.Join(", ", GenericTypeParameters.Select(p => p.GetScopedCSharpName(usingScope))) + ">";
public abstract string GetSignatureString();
public abstract string GetSignatureString(Scope usingScope);
// List of operator overload metadata names
// https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/operator-overloads

View File

@@ -33,7 +33,7 @@ namespace Il2CppInspector.Reflection
// TODO: Generic arguments (and on ConstructorInfo)
public override string ToString() => ReturnType.Name + " " + Name + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")";
public override string GetSignatureString() => ReturnParameter.GetSignatureString() + " " + Name + GetTypeParametersString()
public override string GetSignatureString(Scope usingScope) => ReturnParameter.GetSignatureString() + " " + Name + GetTypeParametersString(usingScope)
+ "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")";
}
}

View File

@@ -37,15 +37,15 @@ namespace Il2CppInspector.Reflection
public bool IsOut => (Attributes & ParameterAttributes.Out) != 0;
public bool IsRetval => (Attributes & ParameterAttributes.Retval) != 0;
// The member in which the parameter is defined
public MemberInfo Member { get; }
// The method in which the parameter is defined
public MethodBase DeclaringMethod { get; }
// Name of parameter
public string Name { get; }
// Type of this parameter
private readonly int paramTypeUsage;
public TypeInfo ParameterType => Member.Assembly.Model.GetTypeFromUsage(paramTypeUsage, MemberTypes.TypeInfo);
public TypeInfo ParameterType => DeclaringMethod.Assembly.Model.GetTypeFromUsage(paramTypeUsage, MemberTypes.TypeInfo);
// Zero-indexed position of the parameter in parameter list
public int Position { get; }
@@ -53,7 +53,7 @@ namespace Il2CppInspector.Reflection
// Create a parameter. Specify paramIndex == -1 for a return type parameter
public ParameterInfo(Il2CppInspector pkg, int paramIndex, MethodBase declaringMethod) {
Index = paramIndex;
Member = declaringMethod;
DeclaringMethod = declaringMethod;
if (paramIndex == -1) {
Position = -1;
@@ -96,15 +96,15 @@ namespace Il2CppInspector.Reflection
+ (IsByRef? "ref " : "")
+ (IsOut? "out " : "");
private string getCSharpSignatureString() => $"{GetModifierString()}{ParameterType.CSharpName}";
private string getCSharpSignatureString(Scope scope) => $"{GetModifierString()}{ParameterType.GetScopedCSharpName(scope)}";
public string GetSignatureString() => $"{GetModifierString()}{ParameterType.FullName}";
public string GetParameterString(bool emitPointer = false, bool compileAttributes = false) => IsRetval? null :
(Position == 0 && Member.GetCustomAttributes("System.Runtime.CompilerServices.ExtensionAttribute").Any()? "this ":"")
+ $"{CustomAttributes.ToString(inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}"
+ $"{getCSharpSignatureString()} {Name}"
public string GetParameterString(Scope usingScope, bool emitPointer = false, bool compileAttributes = false) => IsRetval? null :
(Position == 0 && DeclaringMethod.GetCustomAttributes("System.Runtime.CompilerServices.ExtensionAttribute").Any()? "this ":"")
+ $"{CustomAttributes.ToString(usingScope, inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}"
+ $"{getCSharpSignatureString(usingScope)} {Name}"
+ (HasDefaultValue ? " = " + DefaultValue.ToCSharpValue() + (emitPointer && !(DefaultValue is null)? $" /* Metadata: 0x{(uint) DefaultValueMetadataAddress:X8} */" : "") : "");
public string GetReturnParameterString() => !IsRetval? null : getCSharpSignatureString();
public string GetReturnParameterString(Scope scope) => !IsRetval? null : getCSharpSignatureString(scope);
}
}

View File

@@ -0,0 +1,20 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
namespace Il2CppInspector.Reflection
{
// A code scope with which to evaluate how to output type references
public class Scope
{
// The scope we are currently in
public TypeInfo Current;
// The list of namespace using directives in the file
public IEnumerable<string> Namespaces;
}
}

View File

@@ -143,6 +143,103 @@ namespace Il2CppInspector.Reflection {
+ (IsArray? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsPointer? "*" : "");
// Returns the minimally qualified type name required to refer to this type within the specified scope
public string GetScopedFullName(Scope scope) {
// This is the scope in which this type is currently being used
// If Scope.Current is null, our scope is at the assembly level
var usingScope = scope.Current?.FullName ?? "";
// This is the scope in which this type's definition is located
var declaringScope = DeclaringType?.FullName ?? Namespace;
// If the scope of usage is inside the scope in which the type is declared, no additional scope is needed
if ((usingScope + ".").StartsWith(declaringScope + ".") || (usingScope + "+").StartsWith(declaringScope + "+"))
return base.Name;
// Global (unnamed) namespace?
string scopedName;
if (string.IsNullOrEmpty(declaringScope))
scopedName = base.Name;
// Find first difference in the declaring scope from the using scope, moving one namespace/type name at a time
else {
var diff = 0;
usingScope += ".";
while (usingScope.IndexOf(".", diff) == declaringScope.IndexOf(".", diff)
&& usingScope.IndexOf(".", diff) != -1
&& usingScope.Substring(0, usingScope.IndexOf(".", diff))
== declaringScope.Substring(0, declaringScope.IndexOf(".", diff)))
diff = usingScope.IndexOf(".", diff) + 1;
usingScope = usingScope.Substring(0, usingScope.Length -1) + "+";
while (usingScope.IndexOf("+", diff) == declaringScope.IndexOf("+", diff)
&& usingScope.IndexOf("+", diff) != -1
&& usingScope.Substring(0, usingScope.IndexOf("+", diff))
== declaringScope.Substring(0, declaringScope.IndexOf("+", diff)))
diff = usingScope.IndexOf("+", diff) + 1;
scopedName = declaringScope.Substring(diff) + (DeclaringType != null? "+" : ".") + base.Name;
}
// At this point, scopedName contains the minimum required scope, discounting any using directives
// or whether there are conflicts with any ancestor scope
// Check to see if there is a namespace in our using directives which brings this type into scope
var usingRef = scope.Namespaces.OrderByDescending(n => n.Length).FirstOrDefault(n => scopedName.StartsWith(n + "."));
var minimallyScopedName = usingRef == null ? scopedName : scopedName.Substring(usingRef.Length + 1);
// minimallyScopedName now contains the minimum required scope, taking using directives into account
// Are there any ancestors in the using scope with the same type name as the first part of the minimally scoped name?
// If so, the ancestor type name will hide the type we are trying to reference,
// so we need to provide the scope ignoring any using directives
var firstPart = minimallyScopedName.Split('.')[0].Split('+')[0];
for (var d = scope.Current; d != null; d = d.DeclaringType)
if (d.BaseName == firstPart)
return scopedName.Replace('+', '.');
// If there are multiple using directives that would allow the same minimally scoped name to be used,
// then the minimally scoped name is ambiguous and we can't use it
// NOTE: We should check all the parts, not just the first part, but this works in the vast majority of cases
if (scope.Namespaces.Count(n => Assembly.Model.TypesByFullName.ContainsKey(n + "." + firstPart)) > 1)
return scopedName.Replace('+', '.');
return minimallyScopedName.Replace('+', '.');
}
// C#-friendly type name as it should be used in the scope of a given type
public string GetScopedCSharpName(Scope usingScope = null) {
// Unscoped name if no using scope specified
if (usingScope == null)
return CSharpName;
var s = Namespace + "." + base.Name;
// Built-in keyword type names do not require a scope
var i = Il2CppConstants.FullNameTypeString.IndexOf(s);
var n = i != -1 ? Il2CppConstants.CSharpTypeString[i] : GetScopedFullName(usingScope);
// Unmangle generic type names
if (n?.IndexOf("`", StringComparison.Ordinal) != -1)
n = n?.Remove(n.IndexOf("`", StringComparison.Ordinal));
// Generic type parameters and type arguments
var g = (GenericTypeParameters != null ? "<" + string.Join(", ", GenericTypeParameters.Select(x => x.GetScopedCSharpName(usingScope))) + ">" : "");
g = (GenericTypeArguments != null ? "<" + string.Join(", ", GenericTypeArguments.Select(x => x.GetScopedCSharpName(usingScope))) + ">" : g);
n += g;
// Nullable types
if (s == "System.Nullable`1" && GenericTypeArguments.Any())
n = GenericTypeArguments[0].GetScopedCSharpName(usingScope) + "?";
// Arrays, pointers, references
if (HasElementType)
n = ElementType.GetScopedCSharpName(usingScope);
return n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : "");
}
public GenericParameterAttributes GenericParameterAttributes { get; }
public int GenericParameterPosition { get; }
@@ -607,7 +704,7 @@ namespace Il2CppInspector.Reflection {
return modifiers.ToString();
}
public string GetTypeConstraintsString() {
public string GetTypeConstraintsString(Scope scope) {
if (!IsGenericParameter)
return string.Empty;
@@ -620,7 +717,7 @@ namespace Il2CppInspector.Reflection {
if (DeclaringMethod == null && DeclaringType.IsNested && (DeclaringType.DeclaringType.GenericTypeParameters?.Any(p => p.Name == Name) ?? false))
return string.Empty;
var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.CSharpTypeDeclarationName).ToList();
var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.GetScopedCSharpName(scope)).ToList();
if ((GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint)
constraintList.Add("struct");