in src/Microsoft.Diagnostics.Runtime/Implementation/DACNameParser.cs [770:855]
private static bool TryPatchUnfulfilledGenericArgs(int genericTargetIndex, List<TypeNameSegment> genericArgs)
{
TypeNameSegment targetTypeToPatch = genericArgs[genericTargetIndex];
DebugOnly.Assert(targetTypeToPatch.HasUnfulfilledGenericArgs, "Called TryPatchUnfulfilledGenericArgs with an index pointing at a TypeNameSegment that does not have unfulfilled generic params");
// Patch ALL missing args from the target type, but no more. Any types before the target that need filling in will trigger another ResolveParsedGenericList state
// to be entered as we unwind the parse, and at that point we will propagate types back another level. This is very important to correctly parse horrific types like:
//
// Microsoft.CodeAnalysis.PooledObjects.ObjectPool<System.Collections.Generic.Stack<System.ValueTuple<Microsoft.CodeAnalysis.Shared.Collections.IntervalTree<Microsoft.CodeAnalysis.Editor.Shared.Tagging.TagSpanIntervalTree<Microsoft.VisualStudio.Text.Tagging.IBlockTag>+TagNode>+Node, System.Boolean>>>+Factory
int nextTargetFulfillmentIndex = -1;
while (targetTypeToPatch.HasUnfulfilledGenericArgs)
{
DebugOnly.Assert(genericTargetIndex + 1 != genericArgs.Count, "There are no arguments parsed following the target type for our generic arg back-propagation code. What do we patch with?");
if (genericTargetIndex + 1 == genericArgs.Count)
return false;
// NOTE: This is an annoyance of the DAC. Generally, when patching generic args into their owning type we can simply take the first arg after the generic
// type entry itself and patch away. However, if we have a nested generic type whose parent is also a generic type then the type list for BOTH types are
// given to us in a single flat list, in order from outer to inner.
//
// So for instance, an example of the simple case, is this:
//
// Foo<T1,T2>+Bar
//
// The DAC gives us this Foo`2+Bar[[[T1,assembly],[T2,assembly]]]
//
// In which case we simply patch the list into Foo front to back starting with the first genericArg entry AFTER the generic type are patching into
//
// However, for this:
//
// Foo<T1,T2>+Bar<U>
//
// the DAC will give us Foo`2+Bar`1[[[T1,assembly],[T2,assembly],[U,assembly]]]
//
// In this case the proper argument to patch into Bar is U (not T1). W simply need to skip the # of arguments in the list corresponding to the # of arguments
// all parent types above us will consume from the list.
if (targetTypeToPatch.IsNestedClass && targetTypeToPatch.IsGenericClass && (nextTargetFulfillmentIndex == -1))
{
// The target to use to patch will be at least the arg after the type we are patching (genericTargetIndex + 1), but need to see how many nested and generic
// classes are above us at this same parse level so we can correctly pull arguments ala the rather large explanation above.
nextTargetFulfillmentIndex = genericTargetIndex + 1;
for (int i = genericTargetIndex - 1; i >= 0; i--)
{
// Don't flow back between levels of nested generic lists
if (genericArgs[i].ParsingArgDepth != targetTypeToPatch.ParsingArgDepth)
break;
if (genericArgs[i].IsGenericClass)
nextTargetFulfillmentIndex += genericArgs[i].RemainingUnfulfilledGenericArgCount;
// If the previous class itself is not nested, then we can stop searching, if it is, we have to continue
if (!genericArgs[i].IsNestedClass)
break;
}
}
// Look at every type after the target type that is missing generic parameter info. For any that are NOT nested classes (these are important to skip),
// mark it as the next argument for argument back-propagation.
//
// NOTE: If nextTargetFulfillmentIndex is not -1 it means we can use it as the start position, it USED to point to the last argument we back-propagated, but
// since we back-propagated it and removed it from the genericArgs list it now points at the next potential candidate for continued back-propagation.
for (int i = (nextTargetFulfillmentIndex != -1 ? nextTargetFulfillmentIndex : genericTargetIndex + 1); i < genericArgs.Count; i++)
{
if (!genericArgs[i].IsNestedClass)
{
nextTargetFulfillmentIndex = i;
break;
}
}
DebugOnly.Assert(nextTargetFulfillmentIndex != genericTargetIndex, "Ran out of args to back-propagate to satisfy generic requirements of earlier generic parameter.");
if (nextTargetFulfillmentIndex == genericTargetIndex)
return false;
// Add the located argument as a generic arg to our previous generic type
targetTypeToPatch.AddGenericArg(genericArgs[nextTargetFulfillmentIndex]);
// Remove the back-propagated argument from our argument list since it is now contained within targetTypeToPatch
genericArgs.RemoveAt(nextTargetFulfillmentIndex);
}
// Patch our updated type info now that we have satisfied all of its generic arg requirements
genericArgs[genericTargetIndex] = targetTypeToPatch;
return true;
}