File: EndToEndTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\EndToEnd\Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using Roslyn.Test.Utilities;
using System;
using System.Text;
using Xunit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
using Roslyn.Test.Utilities.TestGenerators;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.EndToEnd
{
    [TestCaseOrderer("XUnit.Project.Orderers.AlphabeticalOrderer", "XUnit.Project")]
    public class EndToEndTests : EmitMetadataTestBase
    {
        /// <summary>
        /// These tests are very sensitive to stack size hence we use a fresh thread to ensure there 
        /// is a consistent stack size for them to execute in. 
        /// </summary>
        /// <param name="action"></param>
        private static void RunInThread(Action action, TimeSpan? timeout = null)
        {
            Exception exception = null;
            var thread = new System.Threading.Thread(() =>
            {
                try
                {
                    action();
                }
                catch (Exception ex)
                {
                    exception = ex;
                }
            }, 0);
 
            thread.Start();
            if (timeout is { } t && !Debugger.IsAttached)
            {
                if (!thread.Join(t))
                {
                    throw new TimeoutException(t.ToString());
                }
            }
            else
            {
                thread.Join();
            }
 
            if (exception is object)
            {
                Assert.False(true, exception.ToString());
            }
        }
 
        private static void RunTest(int expectedDepth, Action<int> runTest)
        {
            if (runTestAndCatch(expectedDepth))
            {
                return;
            }
 
            int minDepth = 0;
            int maxDepth = expectedDepth;
            int actualDepth;
            while (true)
            {
                int depth = (maxDepth - minDepth) / 2 + minDepth;
                if (depth <= minDepth)
                {
                    actualDepth = minDepth;
                    break;
                }
                if (depth >= maxDepth)
                {
                    actualDepth = maxDepth;
                    break;
                }
                if (runTestAndCatch(depth))
                {
                    minDepth = depth;
                }
                else
                {
                    maxDepth = depth;
                }
            }
            Assert.Equal(expectedDepth, actualDepth);
 
            bool runTestAndCatch(int depth)
            {
                try
                {
                    runTest(depth);
                    return true;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }
 
        // This test is a canary attempting to make sure that we don't regress the # of fluent calls that 
        // the compiler can handle. 
        [WorkItem(16669, "https://github.com/dotnet/roslyn/issues/16669")]
        [ConditionalFact(typeof(WindowsOrLinuxOnly)), WorkItem(34880, "https://github.com/dotnet/roslyn/issues/34880")]
        public void OverflowOnFluentCall()
        {
            int numberFluentCalls = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                (4, ExecutionConfiguration.Debug) => 4000,
                (4, ExecutionConfiguration.Release) => 4000,
                (8, ExecutionConfiguration.Debug) => 4000,
                (8, ExecutionConfiguration.Release) => 4000,
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            // <path>\xunit.console.exe "<path>\CSharpCompilerEmitTest\Roslyn.Compilers.CSharp.Emit.UnitTests.dll"  -noshadow -verbose -class "Microsoft.CodeAnalysis.CSharp.UnitTests.Emit.EndToEndTests"
            // <path>\xunit.console.x86.exe "<path>\CSharpCompilerEmitTest\Roslyn.Compilers.CSharp.Emit.UnitTests.dll"  -noshadow -verbose -class "Microsoft.CodeAnalysis.CSharp.UnitTests.Emit.EndToEndTests"
            // Un-comment loop below and use above commands to figure out the new limits
            //for (int i = 0; i < numberFluentCalls; i = i + 10)
            //{
            //    Console.WriteLine($"Depth: {i}");
            //    tryCompileDeepFluentCalls(i);
            //}
 
            tryCompileDeepFluentCalls(numberFluentCalls);
 
            void tryCompileDeepFluentCalls(int depth)
            {
                var builder = new StringBuilder();
                builder.AppendLine(
        @"class C {
    C M(string x) { return this; }
    void M2() {
        global::C.GetC()
");
                for (int i = 0; i < depth; i++)
                {
                    builder.AppendLine(@"            .M(""test"")");
                }
                builder.AppendLine(
                   @"            .M(""test"");
    }
 
    static C GetC() => new C();
}
");
 
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var options = TestOptions.DebugDll.WithConcurrentBuild(false);
                    var compilation = CreateCompilation(source, options: options);
                    compilation.VerifyDiagnostics();
                    compilation.EmitToArray();
                });
            }
        }
 
        // This test is a canary attempting to make sure that we don't regress the # of fluent calls that 
        // the compiler can handle. 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72678"), WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1874763")]
        public void OverflowOnFluentCall_ExtensionMethods()
        {
            int numberFluentCalls = (IntPtr.Size, ExecutionConditionUtil.Configuration, RuntimeUtilities.IsDesktopRuntime, RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) switch
            {
                (8, ExecutionConfiguration.Debug, false, false) => 750,
                (8, ExecutionConfiguration.Release, false, false) => 750, // Should be ~3_400, but is flaky.
                (4, ExecutionConfiguration.Debug, true, false) => 450,
                (4, ExecutionConfiguration.Release, true, false) => 1_600,
                (8, ExecutionConfiguration.Debug, true, false) => 1_100,
                (8, ExecutionConfiguration.Release, true, false) => 3_300,
                (_, _, _, true) => 200,
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}, Desktop: {RuntimeUtilities.IsDesktopRuntime}")
            };
 
            // Un-comment the call below to figure out the new limits.
            //testLimits();
 
            try
            {
                tryCompileDeepFluentCalls(numberFluentCalls);
            }
            catch (Exception e)
            {
                testLimits(e);
            }
 
            void testLimits(Exception innerException = null)
            {
                for (int i = 0; i < int.MaxValue; i += 10)
                {
                    try
                    {
                        tryCompileDeepFluentCalls(i);
                    }
                    catch (Exception e)
                    {
                        if (innerException != null)
                        {
                            e = new AggregateException(e, innerException);
                        }
 
                        throw new Exception($"Depth: {i}, Bytes: {IntPtr.Size}, Config: {ExecutionConditionUtil.Configuration}, Desktop: {RuntimeUtilities.IsDesktopRuntime}", e);
                    }
                }
            }
 
            void tryCompileDeepFluentCalls(int depth)
            {
                var builder = new StringBuilder();
                builder.AppendLine("""
                    static class E
                    {
                        public static C M(this C c, string x) { return c; }
                    }
                    class C
                    {
                        static C GetC() => new C();
                        void M2()
                        {
                            GetC()
                    """);
                for (int i = 0; i < depth; i++)
                {
                    builder.AppendLine(""".M("test")""");
                }
                builder.AppendLine("""; } }""");
 
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var options = TestOptions.DebugDll.WithConcurrentBuild(false);
                    var compilation = CreateCompilation(source, options: options);
                    compilation.VerifyEmitDiagnostics();
                });
            }
        }
 
        [ConditionalFact(typeof(WindowsOrLinuxOnly))]
        [WorkItem(33909, "https://github.com/dotnet/roslyn/issues/33909")]
        [WorkItem(34880, "https://github.com/dotnet/roslyn/issues/34880")]
        [WorkItem(53361, "https://github.com/dotnet/roslyn/issues/53361")]
        public void DeeplyNestedGeneric()
        {
            int nestingLevel = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                // Legacy baselines are indicated by comments
                (4, ExecutionConfiguration.Debug) => 370, // 270
                (4, ExecutionConfiguration.Release) => 1290, // 1290
                (8, ExecutionConfiguration.Debug) => 270, // 170
                (8, ExecutionConfiguration.Release) => 730, // 730
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            // Un-comment loop below and use above commands to figure out the new limits
            //Console.WriteLine($"Using architecture: {ExecutionConditionUtil.Architecture}, configuration: {ExecutionConditionUtil.Configuration}");
            //for (int i = nestingLevel; i < int.MaxValue; i = i + 10)
            //{
            //    var start = DateTime.UtcNow;
            //    Console.Write($"Depth: {i}");
            //    runDeeplyNestedGenericTest(i);
            //    Console.WriteLine($" - {DateTime.UtcNow - start}");
            //}
 
            runDeeplyNestedGenericTest(nestingLevel);
 
            void runDeeplyNestedGenericTest(int nestingLevel)
            {
                var builder = new StringBuilder();
                builder.AppendLine(@"
#pragma warning disable 168 // Unused local
using System;
 
public class Test
{
    public static void Main(string[] args)
    {
");
 
                for (var i = 0; i < nestingLevel; i++)
                {
                    if (i > 0)
                    {
                        builder.Append('.');
                    }
                    builder.Append($"MyStruct{i}<int>");
                }
 
                builder.AppendLine(" local;");
                builder.AppendLine(@"
        Console.WriteLine(""Pass"");
    }
}");
 
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine($"public struct MyStruct{i}<T{i}> {{");
                }
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine("}");
                }
 
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var compilation = CreateCompilation(source, options: TestOptions.DebugExe.WithConcurrentBuild(false));
                    compilation.VerifyDiagnostics();
 
                    // PEVerify is skipped here as it doesn't scale to this level of nested generics. After 
                    // about 600 levels of nesting it will not return in any reasonable amount of time.
                    CompileAndVerify(compilation, expectedOutput: "Pass", verify: Verification.Skipped);
                });
            }
        }
 
        [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/69515")]
        public void GenericInheritanceCascade_CSharp(bool reverse, bool concurrent)
        {
            const int number = 17;
 
            /*
                class C0<T>;
                class C1<T> : C0<T>;
                class C2<T> : C1<T>;
                ...
            */
            var declarations = new string[number];
            declarations[0] = "class C0<T0> { }";
            for (int i = 1; i < number; i++)
            {
                declarations[i] = $$"""class C{{i}}<T{{i}}> : C{{i - 1}}<T{{i}}> { }""";
            }
 
            if (reverse)
            {
                Array.Reverse(declarations);
            }
 
            var source = string.Join(Environment.NewLine, declarations);
            var options = TestOptions.DebugDll.WithConcurrentBuild(concurrent);
 
            RunInThread(() =>
            {
                CompileAndVerify(source, options: options).VerifyDiagnostics();
            }, timeout: TimeSpan.FromSeconds(10));
        }
 
        [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/69515")]
        public void GenericInheritanceCascade_VisualBasic(bool reverse, bool concurrent)
        {
            const int number = 17;
 
            /*
                Class C0(Of T)
                End Class
                Class C1(Of T)
                    Inherits C0(Of T)
                End Class
                Class C2(Of T)
                    Inherits C1(Of T)
                End Class
                ...
            */
            var declarations = new string[number];
            declarations[0] = """
                Class C0(Of T0)
                End Class
                """;
            for (int i = 1; i < number; i++)
            {
                declarations[i] = $"""
                    Class C{i}(Of T{i})
                        Inherits C{i - 1}(Of T{i})
                    End Class
                    """;
            }
 
            if (reverse)
            {
                Array.Reverse(declarations);
            }
 
            var source = string.Join(Environment.NewLine, declarations);
            var options = new VisualBasic.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
                .WithConcurrentBuild(concurrent);
 
            RunInThread(() =>
            {
                CreateVisualBasicCompilation(source, compilationOptions: options).VerifyDiagnostics();
            }, timeout: TimeSpan.FromSeconds(10));
        }
 
        [ConditionalFact(typeof(WindowsOrLinuxOnly), typeof(NoIOperationValidation))]
        public void NestedIfStatements()
        {
            int nestingLevel = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                (4, ExecutionConfiguration.Debug) => 310,
                (4, ExecutionConfiguration.Release) => 1400,
                (8, ExecutionConfiguration.Debug) => 200,
                (8, ExecutionConfiguration.Release) => 474,
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            RunTest(nestingLevel, runTest);
 
            static void runTest(int nestingLevel)
            {
                var builder = new StringBuilder();
                builder.AppendLine(
@"class Program
{
    static bool F(int i) => true;
    static void Main()
    {");
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine(
$@"        if (F({i}))
        {{");
                }
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine("        }");
                }
                builder.AppendLine(
@"    }
}");
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false));
                    comp.VerifyDiagnostics();
                });
            }
        }
 
        [WorkItem("https://github.com/dotnet/roslyn/issues/72393")]
        [ConditionalTheory(typeof(NoIOperationValidation))]
        [InlineData(2)]
#if DEBUG
        [InlineData(2000)]
#else
        [InlineData(5000)]
#endif
        public void NestedIfElse(int n)
        {
            var builder = new System.Text.StringBuilder();
            builder.AppendLine("""
                #nullable enable
                class Program
                {
                    static void F(int i)
                    {
                        if (i == 0) { }
                """);
            for (int i = 0; i < n; i++)
            {
                builder.AppendLine($$"""
                            else if (i == {{i}}) { }
                    """);
            }
            builder.AppendLine("""
                    }
                }
                """);
 
            var source = builder.ToString();
            var comp = CreateCompilation(source);
            comp.VerifyEmitDiagnostics();
 
            var tree = comp.SyntaxTrees.Single();
            var model = comp.GetSemanticModel(tree);
            var node = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().Single();
 
            // Avoid using ControlFlowGraphVerifier.GetControlFlowGraph() since that calls
            // TestOperationVisitor.VerifySubTree() which has quadratic behavior using
            // MemberSemanticModel.GetEnclosingBinderInternalWithinRoot().
            var operation = (Microsoft.CodeAnalysis.Operations.IMethodBodyOperation)model.GetOperation(node);
            var graph = Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(operation);
 
            if (n == 2)
            {
                var symbol = model.GetDeclaredSymbol(node);
                ControlFlowGraphVerifier.VerifyGraph(comp, """
                    Block[B0] - Entry
                        Statements (0)
                        Next (Regular) Block[B1]
                    Block[B1] - Block
                        Predecessors: [B0]
                        Statements (0)
                        Jump if False (Regular) to Block[B2]
                            IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean) (Syntax: 'i == 0')
                              Left:
                                IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'i')
                              Right:
                                ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0')
                        Next (Regular) Block[B4]
                    Block[B2] - Block
                        Predecessors: [B1]
                        Statements (0)
                        Jump if False (Regular) to Block[B3]
                            IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean) (Syntax: 'i == 0')
                              Left:
                                IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'i')
                              Right:
                                ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0')
                        Next (Regular) Block[B4]
                    Block[B3] - Block
                        Predecessors: [B2]
                        Statements (0)
                        Jump if False (Regular) to Block[B4]
                            IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean) (Syntax: 'i == 1')
                              Left:
                                IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'i')
                              Right:
                                ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1')
                        Next (Regular) Block[B4]
                    Block[B4] - Exit
                        Predecessors: [B1] [B2] [B3*2]
                        Statements (0)
                    """,
                    graph, symbol);
            }
        }
 
        [WorkItem(42361, "https://github.com/dotnet/roslyn/issues/42361")]
        [ConditionalFact(typeof(WindowsOrLinuxOnly))]
        public void Constraints()
        {
            int n = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                (4, ExecutionConfiguration.Debug) => 420,
                (4, ExecutionConfiguration.Release) => 1100,
                (8, ExecutionConfiguration.Debug) => 180,
                (8, ExecutionConfiguration.Release) => 400,
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            RunTest(n, runTest);
 
            static void runTest(int n)
            {
                // class C0<T> where T : C1<T> { }
                // class C1<T> where T : C2<T> { }
                // ...
                // class CN<T> where T : C0<T> { }
                var sourceBuilder = new StringBuilder();
                var diagnosticsBuilder = ArrayBuilder<DiagnosticDescription>.GetInstance();
                for (int i = 0; i <= n; i++)
                {
                    int next = (i == n) ? 0 : i + 1;
                    sourceBuilder.AppendLine($"class C{i}<T> where T : C{next}<T> {{ }}");
                    diagnosticsBuilder.Add(Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "T").WithArguments($"C{i}<T>", $"C{next}<T>", "T", "T"));
                }
                var source = sourceBuilder.ToString();
                var diagnostics = diagnosticsBuilder.ToArrayAndFree();
 
                RunInThread(() =>
                {
                    var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false));
                    var type = comp.GetMember<NamedTypeSymbol>("C0");
                    var typeParameter = type.TypeParameters[0];
                    Assert.True(typeParameter.IsReferenceType);
                    comp.VerifyDiagnostics(diagnostics);
                });
            }
        }
 
        [ConditionalFact(typeof(WindowsOrMacOSOnly), Reason = "https://github.com/dotnet/roslyn/issues/69210"), WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1819416")]
        public void LongInitializerList()
        {
            var sb = new StringBuilder();
            sb.AppendLine("""
                    _ = new System.Collections.Generic.Dictionary<string, string>
                    {
                    """);
 
            for (int i = 0; i < 100; i++)
            {
                sb.AppendLine("""    { "a", "b" },""");
            }
 
            sb.AppendLine("};");
 
            var comp = CreateCompilation(sb.ToString());
            var counter = new MemberSemanticModel.MemberSemanticBindingCounter();
            comp.TestOnlyCompilationData = counter;
            comp.VerifyEmitDiagnostics();
            Assert.Equal(0, counter.BindCount);
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var literals = tree.GetRoot().DescendantNodes().OfType<LiteralExpressionSyntax>().ToArray();
            Assert.Equal(200, literals.Length);
            foreach (var literal in literals)
            {
                var type = model.GetTypeInfo(literal).Type;
                Assert.Equal(SpecialType.System_String, type.SpecialType);
            }
 
            Assert.Equal(1, counter.BindCount);
        }
 
        [Fact]
        public void Interceptors()
        {
            const int numberOfInterceptors = 10000;
 
            // write a program which has many intercepted calls.
            // each interceptor is in a different file.
            var files = ArrayBuilder<(string source, string path)>.GetInstance();
 
            // Build a top-level-statements main like:
            //    C.M();
            //    C.M();
            //    C.M();
            //    ...
            var builder = new StringBuilder();
            for (int i = 0; i < numberOfInterceptors; i++)
            {
                builder.AppendLine("C.M();");
            }
 
            files.Add((builder.ToString(), "Program.cs"));
 
            files.Add(("""
                class C
                {
                    public static void M() => throw null!;
                }
 
                namespace System.Runtime.CompilerServices
                {
                    public class InterceptsLocationAttribute : Attribute
                    {
                        public InterceptsLocationAttribute(string path, int line, int column) { }
                    }
                }
                """, "C.cs"));
 
            for (int i = 0; i < numberOfInterceptors; i++)
            {
                files.Add(($$"""
                    using System;
                    using System.Runtime.CompilerServices;
 
                    class C{{i}}
                    {
                        [InterceptsLocation("Program.cs", {{i + 1}}, 3)]
                        public static void M()
                        {
                            Console.WriteLine({{i}});
                        }
                    }
                    """, $"C{i}.cs"));
            }
 
            var verifier = CompileAndVerify(files.ToArrayAndFree(), parseOptions: TestOptions.Regular.WithFeature("InterceptorsNamespaces", "global"), expectedOutput: makeExpectedOutput());
            verifier.VerifyDiagnostics();
 
            string makeExpectedOutput()
            {
                builder.Clear();
                for (int i = 0; i < numberOfInterceptors; i++)
                {
                    builder.AppendLine($"{i}");
                }
                return builder.ToString();
            }
        }
 
        [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/69093")]
        public void NestedLambdas(bool localFunctions)
        {
            const int overloads1Number = 20;
            const int overloads2Number = 10;
 
            /*
                interface I0 { }
                // ...
                interface I9 { }
            */
            var builder1 = new StringBuilder();
            var interfacesNumber = Math.Max(overloads1Number, overloads2Number);
            for (int i = 0; i < interfacesNumber; i++)
            {
                builder1.AppendLine($$"""interface I{{i}} { }""");
            }
 
            /*
                void M1(System.Action<I0> a) { }
                // ...
                void M1(System.Action<I9> a) { }
            */
            var builder2 = new StringBuilder();
            for (int i = 0; i < overloads1Number; i++)
            {
                builder2.AppendLine($$"""void M1(System.Action<I{{i}}> a) { }""");
            }
 
            /*
                void M2(I0 x, System.Func<string, I0> f) { }
                // ...
                void M2(I9 x, System.Func<string, I9> f) { }
            */
            for (int i = 0; i < overloads2Number; i++)
            {
                builder2.AppendLine($$"""void M2(I{{i}} x, System.Func<string, I{{i}}> f) { }""");
            }
 
            // Local functions should be similarly fast as lambdas.
            var inner = localFunctions ? """
                M2(x, L0);
                static I0 L0(string arg) {
                    arg = arg + "0";
                    return default;
                }
                """ : """
                M2(x, static I0 (string arg) => {
                    arg = arg + "0";
                    return default;
                });
                """;
 
            var source = $$"""
                {{builder1}}
                class C
                {
                    {{builder2}}
                    void Main()
                    {
                        M1(x =>
                        {
                            {{inner}}
                        });
                    }
                }
                """;
            RunInThread(() =>
            {
                var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false));
                var data = new LambdaBindingData();
                comp.TestOnlyCompilationData = data;
                comp.VerifyDiagnostics();
                Assert.Equal(localFunctions ? 20 : 40, data.LambdaBindingCount);
            }, timeout: TimeSpan.FromSeconds(5));
        }
 
        [Fact, WorkItem("https://github.com/dotnet/roslyn/pull/70791")]
        public void ForAttributeWithMetadataName_DeepRecursion()
        {
            var deeplyRecursive = string.Join("+", Enumerable.Repeat(""" "a" """, 20_000));
            var source = $$"""
                class Ex
                {
                    void M()
                    {
                        var v ={{deeplyRecursive}};
                    }
                }
 
                [N1.X]
                class C1 { }
                [N2.X]
                class C2 { }
 
                namespace N1
                {
                    class XAttribute : System.Attribute { }
                }
 
                namespace N2
                {
                    class XAttribute : System.Attribute { }
                }
                """;
            var parseOptions = TestOptions.RegularPreview;
            Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDllThrowing, parseOptions: parseOptions);
 
            Assert.Single(compilation.SyntaxTrees);
 
            var generator = new IncrementalGeneratorWrapper(new PipelineCallbackGenerator(ctx =>
            {
                var input = ctx.SyntaxProvider.ForAttributeWithMetadataName(
                    "N1.XAttribute",
                    (node, _) => node is ClassDeclarationSyntax,
                    (context, _) => (ClassDeclarationSyntax)context.TargetNode);
                ctx.RegisterSourceOutput(input, (spc, node) => { });
            }));
 
            GeneratorDriver driver = CSharpGeneratorDriver.Create(
                [generator],
                parseOptions: parseOptions,
                driverOptions: TestOptions.GeneratorDriverOptions);
 
            driver = driver.RunGenerators(compilation);
            var runResult = driver.GetRunResult().Results[0];
 
            Assert.Collection(runResult.TrackedSteps["result_ForAttributeWithMetadataName"],
                step => Assert.True(step.Outputs.Single().Value is ClassDeclarationSyntax { Identifier.ValueText: "C1" }));
        }
 
        [Theory]
        [InlineData("or", "1")]
        [InlineData("and not", "0")]
        public void ManyBinaryPatterns(string pattern, string expectedOutput)
        {
            const string preamble = $"""
                int i = 2;
 
                System.Console.Write(i is
                """;
            string append = $"""

                {pattern} 
                """;
            const string postscript = """
 
                ? 1 : 0);
                """;
 
            const int numBinaryExpressions = 5_000;
 
            var builder = new StringBuilder(preamble.Length + postscript.Length + append.Length * numBinaryExpressions + 5 /* Max num digit characters */ * numBinaryExpressions);
 
            builder.AppendLine(preamble);
 
            builder.Append(0);
 
            for (int i = 1; i < numBinaryExpressions; i++)
            {
                builder.Append(append);
                // Make sure the emitter has to handle lots of nodes
                builder.Append(i * 2);
            }
 
            builder.AppendLine(postscript);
 
            var source = builder.ToString();
            RunInThread(() =>
            {
                var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithConcurrentBuild(false));
                CompileAndVerify(comp, expectedOutput: expectedOutput);
 
                var tree = comp.SyntaxTrees[0];
                var isPattern = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single();
                var model = comp.GetSemanticModel(tree);
                var operation = model.GetOperation(isPattern);
                Assert.NotNull(operation);
 
                for (; operation.Parent is not null; operation = operation.Parent) { }
 
                Assert.NotNull(ControlFlowGraph.Create((IMethodBodyOperation)operation));
            });
        }
    }
}