File: Semantics\IteratorTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Semantic\Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Semantic.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 System.IO;
using Microsoft.CodeAnalysis.Emit;
using Roslyn.Test.Utilities;
using Xunit;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.Test.Utilities;
using System.Linq;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics
{
    public class IteratorTests : CompilingTestBase
    {
        [Fact]
        public void BasicIterators01()
        {
            var text =
@"using System.Collections.Generic;
 
class Test
{
    IEnumerable<int> I()
    {
        yield return 1;
        yield break;
    }
}";
            var comp = CreateCompilation(text);
 
            var i = comp.GetMember<MethodSymbol>("Test.I");
            Assert.True(i.IsIterator);
            Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString());
 
            comp.VerifyDiagnostics();
 
            Assert.True(i.IsIterator);
            Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString());
        }
 
        [Fact]
        public void BasicIterators02()
        {
            var text =
@"using System.Collections.Generic;
 
class Test
{
    IEnumerable<int> I()
    {
        yield return 1;
    }
}";
            var comp = CreateCompilation(text);
            comp.VerifyDiagnostics();
        }
 
        [Fact]
        public void WrongYieldType()
        {
            var text =
@"using System.Collections.Generic;
 
class Test
{
    IEnumerable<int> I()
    {
        yield return 1.1;
        yield break;
    }
}";
            var comp = CreateCompilation(text);
            comp.VerifyDiagnostics(
                // (7,22): error CS0266: Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)
                //         yield return 1.1;
                Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "1.1").WithArguments("double", "int").WithLocation(7, 22)
                );
        }
 
        [Fact]
        public void NoYieldInLambda()
        {
            var text =
@"using System;
using System.Collections.Generic;
 
class Test
{
    IEnumerable<int> I()
    {
        Func<IEnumerable<int>> i = () => { yield break; };
        yield break;
    }
}";
            var comp = CreateCompilation(text);
            comp.VerifyDiagnostics(
                // (8,44): error CS1621: The yield statement cannot be used inside an anonymous method or lambda expression
                //         Func<IEnumerable<int>> i = () => { yield break; };
                Diagnostic(ErrorCode.ERR_YieldInAnonMeth, "yield").WithLocation(8, 44)
                );
        }
 
        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72443")]
        public void YieldInLock_Async()
        {
            var source = """
                using System;
                using System.Collections.Generic;
                using System.Threading.Tasks;
 
                public class C
                {
                    public async Task ProcessValueAsync()
                    {
                        await foreach (int item in GetValuesAsync())
                        {
                            await Task.Yield();
                            Console.Write(item);
                        }
                    }
 
                    private async IAsyncEnumerable<int> GetValuesAsync()
                    {
                        await Task.Yield();
                        lock (this)
                        {
                            for (int i = 0; i < 10; i++)
                            {
                                yield return i;
 
                                if (i == 3)
                                {
                                    yield break;
                                }
                            }
                        }
                    }
                }
                """ + AsyncStreamsTypes;
 
            var comp = CreateCompilationWithTasksExtensions(source);
            CompileAndVerify(comp).VerifyDiagnostics();
        }
 
        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72443")]
        public void YieldInLock_Sync()
        {
            var source = """
                using System;
                using System.Collections.Generic;
                using System.Threading;
 
                object o = new object();
                Console.WriteLine($"Before: {Monitor.IsEntered(o)}");
                using (IEnumerator<int> e = GetValues(o).GetEnumerator())
                {
                    Console.WriteLine($"Inside: {Monitor.IsEntered(o)}");
                    while (e.MoveNext())
                    {
                        Console.WriteLine($"{e.Current}: {Monitor.IsEntered(o)}");
                    }
                    Console.WriteLine($"Done: {Monitor.IsEntered(o)}");
                }
                Console.WriteLine($"After: {Monitor.IsEntered(o)}");
 
                static IEnumerable<int> GetValues(object obj)
                {
                    lock (obj)
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            yield return i;
 
                            if (i == 1)
                            {
                                yield break;
                            }
                        }
                    }
                }
                """;
 
            var expectedOutput = """
                Before: False
                Inside: False
                0: True
                1: True
                Done: False
                After: False
                """;
 
            CompileAndVerify(source, options: TestOptions.ReleaseExe,
                expectedOutput: expectedOutput).VerifyDiagnostics();
        }
 
        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72443")]
        public void YieldInLock_Nested()
        {
            var source = """
                using System.Collections.Generic;
 
                class C
                {
                    IEnumerable<int> M()
                    {
                        yield return 1;
                        lock (this)
                        {
                            yield return 2;
 
                            local();
 
                            IEnumerable<int> local()
                            {
                                yield return 3;
 
                                lock (this)
                                {
                                    yield return 4;
 
                                    yield break;
                                }
                            }
 
                            yield break;
                        }
                    }
                }
                """;
 
            CreateCompilation(source).VerifyDiagnostics();
        }
 
        [WorkItem(546081, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546081")]
        [Fact]
        public void IteratorBlockWithUnreachableCode()
        {
            var text =
@"using System;
using System.Collections;
 
public class Stack : IEnumerable
{
    int value;
    public IEnumerator GetEnumerator() { return BottomToTop.GetEnumerator(); }
 
    public IEnumerable BottomToTop
    {
        get
        {
            throw new Exception();
            yield return value;
        }
    }
 
}
 
class Test
{
    static void Main()
    {
    }
}";
            var comp = CreateCompilation(text);
 
            EmitResult emitResult;
            using (var output = new MemoryStream())
            {
                emitResult = comp.Emit(output, null, null, null);
            }
 
            Assert.True(emitResult.Success);
        }
 
        [WorkItem(546364, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546364")]
        [Fact]
        public void IteratorWithEnumeratorMoveNext()
        {
            var text =
@"using System.Collections;
using System.Collections.Generic;
public class Item
{
}
public class Program
{
    private IEnumerable<Item> DeferredFeedItems(IEnumerator elements, bool hasMoved)
    {
        while (hasMoved)
        {
            object o = elements.Current;
            if (o != null)
            {
                Item target = new Item();
                yield return target;
            }
            hasMoved = elements.MoveNext();
        }
    }
    public static void Main()
    {
    }
}";
            CompileAndVerify(text).VerifyDiagnostics();
        }
 
        [WorkItem(813557, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/813557")]
        [Fact]
        public void IteratorWithDelegateCreationExpression()
        {
            var text =
@"using System.Collections.Generic;
 
delegate void D();
public class Program
{
    private IEnumerable<int> M1()
    {
        D d = new D(M2);
        yield break;
    }
    void M2(int x) {}
    static void M2() {}
 
    public static void Main()
    {
    }
}";
            CompileAndVerify(text).VerifyDiagnostics();
        }
 
        [WorkItem(888254, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/888254")]
        [Fact]
        public void IteratorWithTryCatch()
        {
            var text =
@"using System;
using System.Collections.Generic;
 
namespace RoslynYield
{
    class Program
    {
        static void Main(string[] args)
        {
        }
        private IEnumerable<int> Failure()
        {
            // int x;
            try
            {
                int x;  // int x = 3;
                switch (1)
                {
                    default:
                        x = 4;
                        break;
                }
                Console.Write(x);
            }
            catch (Exception)
            {
            }
 
            yield break;
        }
    }
}";
            CompileAndVerify(text).VerifyDiagnostics();
        }
 
        [WorkItem(888254, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/888254")]
        [Fact]
        public void IteratorWithTryCatchFinally()
        {
            var text =
@"using System;
using System.Collections.Generic;
 
namespace RoslynYield
{
    class Program
    {
        static void Main(string[] args)
        {
        }
        private IEnumerable<int> Failure()
        {
            try
            {
                int x;
                switch (1)
                {
                    default:
                        x = 4;
                        break;
                }
                Console.Write(x);
            }
            catch (Exception)
            {
                int x;
                switch (1)
                {
                    default:
                        x = 4;
                        break;
                }
                Console.Write(x);
            }
            finally
            {
                int x;
                switch (1)
                {
                    default:
                        x = 4;
                        break;
                }
                Console.Write(x);
            }
 
            yield break;
        }
    }
}";
            CompileAndVerify(text).VerifyDiagnostics();
        }
 
        [Fact]
        [WorkItem(5390, "https://github.com/dotnet/roslyn/issues/5390")]
        public void TopLevelYieldReturn()
        {
            // The incomplete statement is intended
            var text = "yield return int.";
            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Script);
            comp.VerifyDiagnostics(
                // (1,18): error CS1001: Identifier expected
                // yield return int.
                Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(1, 18),
                // (1,18): error CS1002: ; expected
                // yield return int.
                Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 18),
                // (1,18): error CS0117: 'int' does not contain a definition for ''
                // yield return int.
                Diagnostic(ErrorCode.ERR_NoSuchMember, "").WithArguments("int", "").WithLocation(1, 18),
                // (1,1): error CS7020: You cannot use 'yield' in top-level script code
                // yield return int.
                Diagnostic(ErrorCode.ERR_YieldNotAllowedInScript, "yield").WithLocation(1, 1));
 
            var tree = comp.SyntaxTrees[0];
            var yieldNode = (YieldStatementSyntax)tree.GetRoot().DescendantNodes().Where(n => n is YieldStatementSyntax).SingleOrDefault();
 
            Assert.NotNull(yieldNode);
            Assert.Equal(SyntaxKind.YieldReturnStatement, yieldNode.Kind());
 
            var model = comp.GetSemanticModel(tree);
            var typeInfo = model.GetTypeInfo(yieldNode.Expression);
 
            Assert.Equal(TypeKind.Error, typeInfo.Type.TypeKind);
        }
 
        [Fact]
        [WorkItem(5390, "https://github.com/dotnet/roslyn/issues/5390")]
        public void TopLevelYieldBreak()
        {
            var text = "yield break;";
            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Script);
            comp.VerifyDiagnostics(
                // (1,1): error CS7020: You cannot use 'yield' in top-level script code
                // yield break;
                Diagnostic(ErrorCode.ERR_YieldNotAllowedInScript, "yield").WithLocation(1, 1));
 
            var tree = comp.SyntaxTrees[0];
            var yieldNode = (YieldStatementSyntax)tree.GetRoot().DescendantNodes().Where(n => n is YieldStatementSyntax).SingleOrDefault();
 
            Assert.NotNull(yieldNode);
            Assert.Equal(SyntaxKind.YieldBreakStatement, yieldNode.Kind());
        }
 
        [Fact]
        [WorkItem(11649, "https://github.com/dotnet/roslyn/issues/11649")]
        public void IteratorRewriterShouldNotRewriteBaseMethodWrapperSymbol()
        {
            var text =
@"using System.Collections.Generic;
 
class Base
{
    protected virtual IEnumerable<int> M()
    {
        yield break;
    }
 
    class D : Base
    {
        protected override IEnumerable<int> M()
        {
            base.M(); // the rewriting of D.M() synthesizes a BaseMethodWrapperSymbol for this base call, with return type IEnumerable,
                      // but it should not in turn be lowered by the IteratorRewriter
            yield break;
        }
    }
}";
            var comp = CreateCompilation(text, options: TestOptions.DebugDll);
            comp.VerifyEmitDiagnostics(); // without the fix for bug 11649, the compilation would fail emitting
            CompileAndVerify(comp);
        }
 
        [Fact]
        [WorkItem(11649, "https://github.com/dotnet/roslyn/issues/11649")]
        public void IteratorRewriterShouldNotRewriteBaseMethodWrapperSymbol2()
        {
            var source =
@"using System.Collections.Generic;
 
class Base
{
    public static void Main()
    {
        System.Console.WriteLine(string.Join("","", new D().M()));
    }
 
    protected virtual IEnumerable<int> M()
    {
        yield return 1;
        yield return 2;
        yield break;
    }
 
    class D : Base
    {
        protected override IEnumerable<int> M()
        {
            yield return 0;
            foreach (var n in base.M())
            {
                yield return n;
            }
            yield return 3;
            yield break;
        }
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "0,1,2,3", options: TestOptions.DebugExe);
            comp.Compilation.VerifyDiagnostics();
        }
 
        [CompilerTrait(CompilerFeature.IOperation)]
        [Fact]
        [WorkItem(261047, "https://devdiv.visualstudio.com/DevDiv/_workitems?id=261047&_a=edit")]
        public void MissingExpression()
        {
            var text =
@"using System.Collections.Generic;
 
class Test
{
    IEnumerable<int> I()
    {
        yield return;
    }
}";
            var comp = CreateCompilation(text);
            comp.VerifyDiagnostics(
                // (7,15): error CS1627: Expression expected after yield return
                //         yield return;
                Diagnostic(ErrorCode.ERR_EmptyYield, "return").WithLocation(7, 15)
                );
 
            var tree = comp.SyntaxTrees.Single();
            var node = tree.GetRoot().DescendantNodes().OfType<YieldStatementSyntax>().First();
 
            Assert.Equal("yield return;", node.ToString());
 
            comp.VerifyOperationTree(node, expectedOperationTree:
@"
IReturnOperation (OperationKind.YieldReturn, Type: null, IsInvalid) (Syntax: 'yield return;')
  ReturnedValue: 
    IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: 'yield return;')
      Children(0)
");
        }
 
        [Fact]
        [WorkItem(3825, "https://github.com/dotnet/roslyn/issues/3825")]
        public void ObjectCreationExpressionSyntax_01()
        {
            var text = @"
using System.Collections.Generic;
 
class Test<TKey, TValue>
{
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(KeyValuePair<TKey, TValue> kvp)
    {
        yield return new KeyValuePair<TKey, TValue>(kvp.Key, kvp.Value);
    }
}";
            var comp = CreateCompilationWithMscorlib461(text);
            comp.VerifyDiagnostics();
 
            var tree = comp.SyntaxTrees[0];
            var node = tree.GetRoot().DescendantNodes().OfType<ObjectCreationExpressionSyntax>().Single();
 
            Assert.Equal("new KeyValuePair<TKey, TValue>(kvp.Key, kvp.Value)", node.ToString());
 
            var model = comp.GetSemanticModel(tree);
            var typeInfo = model.GetTypeInfo(node);
            var symbolInfo = model.GetSymbolInfo(node);
 
            Assert.Null(model.GetDeclaredSymbol(node));
            Assert.Equal("System.Collections.Generic.KeyValuePair<TKey, TValue>", typeInfo.Type.ToTestDisplayString());
            Assert.Equal(typeInfo.Type, typeInfo.ConvertedType);
            Assert.True(model.GetConversion(node).IsIdentity);
 
            Assert.Equal("System.Collections.Generic.KeyValuePair<TKey, TValue>..ctor(TKey key, TValue value)", symbolInfo.Symbol.ToTestDisplayString());
        }
 
        [Fact]
        [WorkItem(3825, "https://github.com/dotnet/roslyn/issues/3825")]
        public void ObjectCreationExpressionSyntax_02()
        {
            var text = @"
using System.Collections.Generic;
 
class Test<TKey, TValue>
{
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(KeyValuePair<TKey, TValue> kvp)
    {
        yield return new KeyValuePair<TKey, TValue>(kvp, kvp.Value);
    }
}";
            var comp = CreateCompilationWithMscorlib461(text);
            comp.VerifyDiagnostics(
                // (8,53): error CS1503: Argument 1: cannot convert from 'System.Collections.Generic.KeyValuePair<TKey, TValue>' to 'TKey'
                //         yield return new KeyValuePair<TKey, TValue>(kvp, kvp.Value);
                Diagnostic(ErrorCode.ERR_BadArgType, "kvp").WithArguments("1", "System.Collections.Generic.KeyValuePair<TKey, TValue>", "TKey").WithLocation(8, 53)
                );
 
            var tree = comp.SyntaxTrees[0];
            var node = tree.GetRoot().DescendantNodes().OfType<ObjectCreationExpressionSyntax>().Single();
 
            Assert.Equal("new KeyValuePair<TKey, TValue>(kvp, kvp.Value)", node.ToString());
 
            var model = comp.GetSemanticModel(tree);
            var typeInfo = model.GetTypeInfo(node);
            var symbolInfo = model.GetSymbolInfo(node);
 
            Assert.Null(model.GetDeclaredSymbol(node));
            Assert.Equal("System.Collections.Generic.KeyValuePair<TKey, TValue>", typeInfo.Type.ToTestDisplayString());
            Assert.Equal(typeInfo.Type, typeInfo.ConvertedType);
            Assert.True(model.GetConversion(node).IsIdentity);
 
            Assert.Null(symbolInfo.Symbol);
            Assert.Contains("System.Collections.Generic.KeyValuePair<TKey, TValue>..ctor(TKey key, TValue value)", symbolInfo.CandidateSymbols.Select(c => c.ToTestDisplayString()));
            Assert.Equal(CandidateReason.OverloadResolutionFailure, symbolInfo.CandidateReason);
        }
    }
}