Skip to content

Commit

Permalink
improve re-mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
saucecontrol committed Nov 9, 2023
1 parent f147df6 commit 428d65a
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 42 deletions.
75 changes: 68 additions & 7 deletions src/InheritDoc/CecilExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static class CecilExtensions
// http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/DocumentationComments/DocumentationCommentIDVisitor.cs
// http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/DocumentationComments/DocumentationCommentIDVisitor.PartVisitor.cs
// Different compilers/tools generate different encodings, so we generate a list of candidates that includes each style.
public static string GetDocID(this TypeDefinition t) => encodeTypeName(t).Select(t => "T:" + t).First();
public static string GetDocID(this TypeReference t) => encodeTypeName(t).Select(t => "T:" + t).First();

public static IEnumerable<string> GetDocID(this EventDefinition e) => encodeTypeName(e.DeclaringType).SelectMany(t => encodeMemberName(e.Name).Select(m => "E:" + t + "." + m));

Expand Down Expand Up @@ -158,6 +158,68 @@ public static IEnumerable<MethodDefinition> GetBaseCandidates(this MethodDefinit
yield return md;
}

public static TypeReference? GetNextBase(this TypeDefinition t, TypeDefinition bt)
{
var rb = t;
do rb = rb.BaseType?.Resolve();
while (rb is not null && rb != bt);

if (rb == bt)
return t.BaseType!;

foreach (var i in t.Interfaces)
{
rb = i.InterfaceType.Resolve();
if (rb == bt || rb.GetNextBase(bt) is not null)
return i.InterfaceType;
}

return null;
}

public static Dictionary<string, TypeReference>? GetTypeParamMap(this TypeDefinition t, TypeDefinition? bt)
{
if (!t.HasGenericParameters && !(bt?.HasGenericParameters).GetValueOrDefault())
return null;

var ctm = new Dictionary<string, TypeReference>();

if ((bt?.HasGenericParameters).GetValueOrDefault())
{
var stack = new Stack<TypeReference>();
var nbt = (TypeReference)t;
var nrt = t;
do
{
nbt = nrt.GetNextBase(bt!);
if (nbt is null)
break;

nrt = nbt.Resolve();
stack.Push(nbt);
}
while (nrt != bt);

if (stack.Count != 0 && stack.Pop() is GenericInstanceType gi && gi.Resolve() == bt)
{
foreach (var gp in bt!.GenericParameters)
ctm[gp.Name] = gi.GenericArguments[gp.Position];
}

foreach (var gb in stack.Where(b => b.IsGenericInstance).Cast<GenericInstanceType>())
{
var rb = gb.Resolve();
foreach (var kv in ctm.Where(e => e.Value.IsGenericParameter && rb.GenericParameters.Contains(e.Value)).ToList())
ctm[kv.Key] = gb.GenericArguments[rb.GenericParameters.IndexOf((GenericParameter)kv.Value)];
}
}

foreach (var gp in t.GenericParameters.Where(p => !ctm.ContainsKey(p.Name)))
ctm.Add(gp.Name, gp);

return ctm;
}

private static ApiLevel getApiLevel(MethodDefinition? m) => m is null ? ApiLevel.None : m.IsPrivate ? ApiLevel.Private : m.IsAssembly || m.IsFamilyAndAssembly ? ApiLevel.Internal : ApiLevel.Public;

private static ApiLevel getApiLevel(FieldDefinition? f) => f is null ? ApiLevel.None : f.IsPrivate ? ApiLevel.Private : f.IsAssembly || f.IsFamilyAndAssembly ? ApiLevel.Internal : ApiLevel.Public;
Expand All @@ -172,8 +234,7 @@ private static IEnumerable<MethodDefinition> getBaseCandidatesFromType(MethodDef

while (bt is not null)
{
var rbt = (bt.IsGenericInstance ? ((GenericInstanceType)bt).ElementType : bt).Resolve();

var rbt = bt.Resolve();
if (bt.IsGenericInstance)
{
var gi = (GenericInstanceType)bt;
Expand Down Expand Up @@ -235,7 +296,7 @@ private static IEnumerable<string> encodeMethodParams(ICollection<ParameterDefin
foreach (var pl in mp.Select(p => encodeTypeName(p.ParameterType)))
sl = sl.SelectMany(s => pl.Select(p => s + "," + p));

foreach (var s in sl)
foreach (string s in sl)
yield return "(" + s.Substring(1) + ")";
}

Expand Down Expand Up @@ -330,11 +391,11 @@ public static RefAssemblyResolver Create(string mainAssembly, string[] refAssemb

private RefAssemblyResolver() { }

private bool isCompatibleName(AssemblyNameReference name, AssemblyNameReference cname) =>
cname.Name == name.Name && cname.PublicKeyToken.SequenceEqual(name.PublicKeyToken) && cname.Version >= name.Version;

public AssemblyDefinition Resolve(AssemblyNameReference name)
{
static bool isCompatibleName(AssemblyNameReference name, AssemblyNameReference cname) =>
cname.Name == name.Name && cname.PublicKeyToken.SequenceEqual(name.PublicKeyToken) && cname.Version >= name.Version;

if (!cache.TryGetValue(name.FullName, out var match))
cache[name.FullName] = match = cache.Values.FirstOrDefault(c => isCompatibleName(name, c.Name));

Expand Down
78 changes: 43 additions & 35 deletions src/InheritDoc/InheritDocProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ private static class DocElementNames
public static readonly XName Members = XName.Get("members");
public static readonly XName Param = XName.Get("param");
public static readonly XName TypeParam = XName.Get("typeparam");
public static readonly XName TypeParamRef = XName.Get("typeparamref");
public static readonly XName Overloads = XName.Get("overloads");
public static readonly XName Redirect = XName.Get("redirect");
public static readonly XName Returns = XName.Get("returns");
Expand Down Expand Up @@ -338,27 +339,54 @@ static void removeDoc(List<XNode> nodes, int pos)
continue;

var ename = elem.Name;
string? pname = (string)elem.Attribute(DocAttributeNames.Name);

if (ename == DocElementNames.Returns && !dm.HasReturn || ename == DocElementNames.Value && !dm.HasValue)
{
removeDoc(nodes, i);
continue;
}

if (ename == DocElementNames.Param || ename == DocElementNames.TypeParam)
if (ename == DocElementNames.Param)
{
string? pname = (string)elem.Attribute(DocAttributeNames.Name);
var pmap = ename == DocElementNames.Param ? dm.ParamMap : dm.TypeParamMap;

if (!pmap.ContainsKey(pname))
if (!dm.ParamMap.ContainsKey(pname))
{
removeDoc(nodes, i);
continue;
}

elem.SetAttributeValue(DocAttributeNames.Name, pmap[pname]);
elem.SetAttributeValue(DocAttributeNames.Name, dm.ParamMap[pname]);
foreach (var pref in nodes.OfType<XElement>().DescendantNodesAndSelf().OfType<XElement>().Where(e => e.Name == (ename.LocalName + "ref") && (string)e.Attribute(DocAttributeNames.Name) == pname))
pref.SetAttributeValue(DocAttributeNames.Name, pmap[pname]);
pref.SetAttributeValue(DocAttributeNames.Name, dm.ParamMap[pname]);
}

if (ename == DocElementNames.TypeParam)
{
if (!dm.TypeParamMap.ContainsKey(pname) || dm.TypeParamMap[pname] is not GenericParameter)
{
removeDoc(nodes, i);
continue;
}

elem.SetAttributeValue(DocAttributeNames.Name, dm.TypeParamMap[pname].Name);
}

foreach (var tpr in elem.Descendants(DocElementNames.TypeParamRef).ToList())
{
if (!tpr.HasAttribute(DocAttributeNames.Name) || !dm.TypeParamMap.ContainsKey((string)tpr.Attribute(DocAttributeNames.Name)))
{
if ((tpr.PreviousNode?.IsWhiteSpace()).GetValueOrDefault())
tpr.PreviousNode!.Remove();

tpr.Remove();
continue;
}

var tr = dm.TypeParamMap[(string)tpr.Attribute(DocAttributeNames.Name)];
if (tr.IsGenericParameter)
tpr.SetAttributeValue(DocAttributeNames.Name, tr.Name);
else
tpr.ReplaceWith(new XElement("see", new XAttribute("cref", tr.GetDocID())));
}

// Doc inheritance rules built for compatibility with SHFB modulo the name change of the "select" attribute to "path"
Expand Down Expand Up @@ -504,36 +532,16 @@ private static string getTypeIDFromDocID(string docID)
private class DocMatch(string cref)
{
private static readonly IReadOnlyDictionary<string, string> emptyMap = new Dictionary<string, string>();
private static readonly IReadOnlyDictionary<string, TypeReference> emptyTypeMap = new Dictionary<string, TypeReference>();

public string Cref = cref;
public IReadOnlyDictionary<string, string> TypeParamMap = emptyMap;
public IReadOnlyDictionary<string, string> ParamMap = emptyMap;
public IReadOnlyDictionary<string, TypeReference> TypeParamMap = emptyTypeMap;
public bool HasReturn = false;
public bool HasValue = false;

public DocMatch(string cref, TypeReference t, TypeReference? bt = null) : this(cref)
{
if (t.HasGenericParameters && (bt?.IsGenericInstance ?? true))
{
var tpm = new Dictionary<string, string>();

if (bt is not null)
{
var ga = ((GenericInstanceType)bt).GenericArguments;
var rbt = bt.Resolve();

foreach (var tp in t.GenericParameters.Where(ga.Contains))
tpm.Add(rbt.GenericParameters[ga.IndexOf(tp)].Name, tp.Name);
}
else
{
foreach (var tp in t.GenericParameters)
tpm.Add(tp.Name, tp.Name);
}

TypeParamMap = tpm;
}
}
public DocMatch(string cref, TypeDefinition t, TypeReference? bt = null) : this(cref) =>
TypeParamMap = t.GetTypeParamMap(bt?.Resolve()) ?? emptyTypeMap;

public DocMatch(string cref, MethodDefinition m, MethodDefinition? bm = null) : this(cref)
{
Expand All @@ -546,14 +554,14 @@ public DocMatch(string cref, MethodDefinition m, MethodDefinition? bm = null) :
ParamMap = pm;
}

var tpm = m.DeclaringType.GetTypeParamMap(bm?.DeclaringType);
if (m.HasGenericParameters)
{
var tpm = new Dictionary<string, string>();
tpm ??= [ ];
foreach (var tp in m.GenericParameters)
tpm.Add(bm?.GenericParameters[tp.Position].Name ?? tp.Name, tp.Name);

TypeParamMap = tpm;
tpm[bm?.GenericParameters[tp.Position].Name ?? tp.Name] = tp;
}
TypeParamMap = tpm ?? emptyTypeMap;

HasReturn = m.HasReturnValue();
HasValue = m.IsPropertyMethod();
Expand Down

0 comments on commit 428d65a

Please sign in to comment.