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 progate 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-propagaeted 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); }
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; } } DebugOnly.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; DebugOnly.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)); } }