Repository: dotnet/roslyn
File: src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs (C#)

Code before:
                typePredicate: typePredicate,
                arg: arg,
                returnOnMatch: true,
                canDigThroughNullable: canDigThroughNullable,
                useDefaultType: useDefaultType,
                visitCustomModifiers: visitCustomModifiers);
            return matchigSymbols.FirstOrDefault();
        }

        public static List<TypeSymbol> VisitType<T>(
            this TypeWithAnnotations typeWithAnnotationsOpt,
            TypeSymbol? type,
            Func<TypeWithAnnotations, T, bool, bool>? typeWithAnnotationsPredicate,
            Func<TypeSymbol, T, bool, bool>? typePredicate,
            T arg,
            bool returnOnMatch,
            bool canDigThroughNullable = false,
            bool useDefaultType = false,
            bool visitCustomModifiers = false)
        {
            RoslynDebug.Assert(typeWithAnnotationsOpt.HasType == (type is null));
            RoslynDebug.Assert(canDigThroughNullable == false || useDefaultType == false, "digging through nullable will cause early resolution of nullable types");
            RoslynDebug.Assert(canDigThroughNullable == false || visitCustomModifiers == false);
            RoslynDebug.Assert(visitCustomModifiers == false || typePredicate is { });

            List<TypeSymbol> combinedResult = [];

            // In order to handle extremely "deep" types like "int[][][][][][][][][]...[]"
            // or int*****************...* we implement manual tail recursion rather than 
            // doing the natural recursion.

            while (true)
            {
                TypeSymbol current = type ?? (useDefaultType ? typeWithAnnotationsOpt.DefaultType : typeWithAnnotationsOpt.Type);
                bool isNestedNamedType = false;

                // Visit containing types from outer-most to inner-most.
                switch (current.TypeKind)
                {
                    case TypeKind.Class:
                    case TypeKind.Struct:
                    case TypeKind.Interface:
                    case TypeKind.Enum:
                    case TypeKind.Delegate:
                        {
                            var containingType = current.ContainingType;
                            if ((object)containingType != null)
                            {
                                isNestedNamedType = true;
                                var result = VisitType(default, containingType, typeWithAnnotationsPredicate, typePredicate, arg, returnOnMatch, canDigThroughNullable, useDefaultType, visitCustomModifiers);
                                combinedResult.AddRange(result);

Review comment (suggestion):
It feels like this could hurt perf if this method (`VisitType`) is used often which I think it is.

Code after:
                    if (typeWithAnnotationsPredicate(typeWithAnnotationsOpt, arg, isNestedNamedType))
                    {
                        return current;
                    }
                }
                else if (typePredicate != null)
                {
                    if (typePredicate(current, arg, isNestedNamedType))
                    {
                        return current;
                    }
                }

                if (visitCustomModifiers && typeWithAnnotationsOpt.HasType)
                {
                    foreach (var customModifier in typeWithAnnotationsOpt.CustomModifiers)
                    {
                        var result = VisitType(
                            typeWithAnnotationsOpt: default, type: ((CSharpCustomModifier)customModifier).ModifierSymbol,
                            typeWithAnnotationsPredicate, typePredicate, arg,
                            canDigThroughNullable, useDefaultType, visitCustomModifiers);
                        if (result is object)
                        {
                            return result;
                        }
                    }
                }

                TypeWithAnnotations next;

                switch (current.TypeKind)
                {
                    case TypeKind.Dynamic:
                    case TypeKind.TypeParameter:
                    case TypeKind.Submission:
                    case TypeKind.Enum:
                        return null;

                    case TypeKindInternal.FunctionType:
                        if (((FunctionTypeSymbol)current).GetInternalDelegateType() is not { } delegateType)
                        {
                            return null;
                        }

                        current = delegateType;
                        goto case TypeKind.Delegate;

                    case TypeKind.Error:
                    case TypeKind.Class:
                    case TypeKind.Struct:
                    case TypeKind.Interface: