in src/Microsoft.Diagnostics.Runtime/src/Common/DACNameParser.cs [279:414]
private static (ParsingState State, int CurrentPosition) ResolveParsedGenericList(string name, int currentPosition, int parsingGenericArgListDepth, List<TypeNameSegment>? nameSegments, List<TypeNameSegment>? genericArgs)
{
if(genericArgs == null)
{
return (ParsingState.Error, currentPosition);
}
// This is the most complicated part of the state machine, it involves back-propagating completed generic argument types into previous types they belong to.
// It has to take care to propagate both amongst the genericArgs list as well as into the nameSegments list, it also has to ensure it unifies nested classes that
// exist seperate from their parent type, before back-propagating that parent type.
//
// NOTE: This is called one time per generic list, so in the case of nested generics (where a param to a generic is itself another generic) this will be called
// twice (and so on and so forth for arbitrary levels of nesting). What that means is that each call we only want to clear as many generics off our queue
// as the generic types encountered on this parsing level require (whether that is in the generic arg list or the name segment list). And if we roll up generic
// params into other entries in the generic param list we DON'T want to propagate anything to the name segment list since this generic arg list must be part of a nested
// generic situation, not the top-level type name parsing
int genericTargetIndex = -1;
bool propagatedTypesToGenericArgs = false;
// In some cases we end up with a genericArgs list where one entry is the fulfillment of another entry's generic args, this happens in cases like this
//
// System.Action`1[[System.Collections.Generic.IEnumerable`1[[Microsoft.VisualStudio.RemoteSettings.ActionResponse, Microsoft.VisualStudio.Telemetry]], mscorlib]]
//
// In this case System.Action is sitting on the nameSegment list waiting for its generic params, BUT our genericArgs stack has two entries, one for IEnumerable
// (also waiting for its params) and one for ActionResponse, which is the arg to pair with the IEnumerable. So we have handle this arg rollup before we can
// propagate the args to the nameSegments list
//
// NOTE: It is important to do this walk backwards since our list is being used like a queue and later entries bind with entries before them during genric arg
// back-propagation
//
// NOTE: Purposely not using FindLastIndexOf because want to avoid allocation cost of lambda + indirection cost of callback during search
genericTargetIndex = -1;
for (int i = genericArgs.Count - 1; i >= 0; i--)
{
TypeNameSegment target = genericArgs[i];
if (target.HasUnfulfilledGenericArgs && target.ParsingArgDepth == parsingGenericArgListDepth)
{
genericTargetIndex = i;
break;
}
}
while (genericTargetIndex != -1)
{
TypeNameSegment targetSegment = genericArgs[genericTargetIndex];
propagatedTypesToGenericArgs = true;
if (!TryPatchUnfulfilledGenericArgs(genericTargetIndex, genericArgs))
return (ParsingState.Error, currentPosition);
int previousTarget = genericTargetIndex;
genericTargetIndex = -1;
for (int i = previousTarget - 1; i >= 0; i--)
{
TypeNameSegment target = genericArgs[i];
if (target.HasUnfulfilledGenericArgs && target.ParsingArgDepth == parsingGenericArgListDepth)
{
genericTargetIndex = i;
break;
}
}
}
// Roll up any nested classes at this level into their parents
UnifyNestedClasses(parsingGenericArgListDepth, genericArgs);
// If we haven't done any propagation amongst the generic args or we have but we have no more levels of generic args to parse, then we need to propagate
// back into the top level type list, so find the appropriate type entry in that list and propagate args back to it to fulfill missing generics.
if (!propagatedTypesToGenericArgs || (parsingGenericArgListDepth == 0))
{
if(nameSegments == null)
{
Debug.Fail("Ended resolving generic arg list but no top-level types to propagate them to.");
return (ParsingState.Error, currentPosition);
}
// Fill the nameSegment generics with args, in order, from the genericArgs list. This works correctly whether the nameSegments list is a single generic or a
// generic with a nested generic (so WeakKeyDictionary<T1,T2>+<WeakReference<T1>), unlike the special casing for such a situation we need to do while fixing up
// the generic args list.
int targetSegmentIndex = -1;
for(int i = 0; i < nameSegments.Count; i++)
{
if(nameSegments[i].HasUnfulfilledGenericArgs)
{
targetSegmentIndex = i;
break;
}
}
Debug.Assert(targetSegmentIndex != -1, "Ended resolving generic arg list but failed to find any top-level types marked as having unfulfilled generic args to propagate them to.");
if (targetSegmentIndex != -1)
{
TypeNameSegment targetSegment = nameSegments[targetSegmentIndex];
while (genericArgs.Count != 0)
{
targetSegment.AddGenericArg(genericArgs[0]);
genericArgs.RemoveAt(0);
if (!targetSegment.HasUnfulfilledGenericArgs && (genericArgs.Count != 0))
{
// NOTE: TypeNameSegment is a struct to avoid heap allocations, that means we have to extract / modify / re-store to ensure the updated state gets back into whatever
// list this came from.
nameSegments[targetSegmentIndex] = targetSegment;
targetSegmentIndex = nameSegments.FindIndex(targetSegmentIndex, (tns) => tns.HasUnfulfilledGenericArgs);
if (targetSegmentIndex == -1)
{
return (ParsingState.Error, currentPosition);
}
targetSegment = nameSegments[targetSegmentIndex];
}
}
// NOTE: TypeNameSegment is a struct to avoid heap allocations, that means we have to extract / modify / re-store to ensure the updated state gets back into whatever
// list this came from.
nameSegments[targetSegmentIndex] = targetSegment;
Debug.Assert(genericArgs.Count == 0, "Back-propagation to top-level generic types ended with generic args still in the genericArgs list.");
return DetermineNextStateAndPos(name, currentPosition);
}
else
{
return (ParsingState.Error, currentPosition);
}
}
else
{
return DetermineNextStateAndPos(name, currentPosition);
}
}