From 428d65aa8bb27552d59b418ef26db3ed081edfc4 Mon Sep 17 00:00:00 2001 From: Clinton Ingram Date: Wed, 8 Nov 2023 20:48:18 -0800 Subject: [PATCH] improve re-mapping --- src/InheritDoc/CecilExtensions.cs | 75 +++++++++++++++++++++++--- src/InheritDoc/InheritDocProcessor.cs | 78 +++++++++++++++------------ 2 files changed, 111 insertions(+), 42 deletions(-) diff --git a/src/InheritDoc/CecilExtensions.cs b/src/InheritDoc/CecilExtensions.cs index 0a38f0d..8ab176d 100644 --- a/src/InheritDoc/CecilExtensions.cs +++ b/src/InheritDoc/CecilExtensions.cs @@ -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 GetDocID(this EventDefinition e) => encodeTypeName(e.DeclaringType).SelectMany(t => encodeMemberName(e.Name).Select(m => "E:" + t + "." + m)); @@ -158,6 +158,68 @@ public static IEnumerable 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? GetTypeParamMap(this TypeDefinition t, TypeDefinition? bt) + { + if (!t.HasGenericParameters && !(bt?.HasGenericParameters).GetValueOrDefault()) + return null; + + var ctm = new Dictionary(); + + if ((bt?.HasGenericParameters).GetValueOrDefault()) + { + var stack = new Stack(); + 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()) + { + 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; @@ -172,8 +234,7 @@ private static IEnumerable 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; @@ -235,7 +296,7 @@ private static IEnumerable encodeMethodParams(ICollection 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) + ")"; } @@ -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)); diff --git a/src/InheritDoc/InheritDocProcessor.cs b/src/InheritDoc/InheritDocProcessor.cs index bc0dad8..be19dfe 100644 --- a/src/InheritDoc/InheritDocProcessor.cs +++ b/src/InheritDoc/InheritDocProcessor.cs @@ -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"); @@ -338,6 +339,7 @@ static void removeDoc(List 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) { @@ -345,20 +347,46 @@ static void removeDoc(List nodes, int pos) 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().DescendantNodesAndSelf().OfType().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" @@ -504,36 +532,16 @@ private static string getTypeIDFromDocID(string docID) private class DocMatch(string cref) { private static readonly IReadOnlyDictionary emptyMap = new Dictionary(); + private static readonly IReadOnlyDictionary emptyTypeMap = new Dictionary(); public string Cref = cref; - public IReadOnlyDictionary TypeParamMap = emptyMap; public IReadOnlyDictionary ParamMap = emptyMap; + public IReadOnlyDictionary 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(); - - 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) { @@ -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(); + 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();