diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs index c3c9b8c4b1d61..a2dd62da74305 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs @@ -328,6 +328,8 @@ INamedTypeSymbol IMethodSymbol.AssociatedAnonymousDelegate bool IMethodSymbol.IsConditional => _underlying.IsConditional; + bool IMethodSymbol.IsIterator => _underlying.IsIterator; + DllImportData IMethodSymbol.GetDllImportData() => _underlying.GetDllImportData(); #region ISymbol Members diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index 69f12e7e3d7e2..0692049f45402 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.Test.Utilities; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { @@ -35,12 +36,16 @@ IEnumerable I() var comp = CreateCompilation(text); var i = comp.GetMember("Test.I"); + IMethodSymbol publicI = i.GetPublicSymbol(); + Assert.True(i.IsIterator); + Assert.True(publicI.IsIterator); Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); comp.VerifyDiagnostics(); Assert.True(i.IsIterator); + Assert.True(publicI.IsIterator); Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); } @@ -61,6 +66,102 @@ IEnumerable I() comp.VerifyDiagnostics(); } + [Fact] + public void BasicIterators_Async() + { + var source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + + class Test + { + async IAsyncEnumerable I() + { + await Task.Yield(); + yield return 1; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var i = comp.GetMember("Test.I"); + Assert.True(i.IsIterator); + Assert.True(i.GetPublicSymbol().IsIterator); + Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); + } + + [Fact] + public void BasicIterators_Metadata() + { + var source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + + public class Test + { + public IEnumerable I1() + { + yield return 1; + } + + public async IAsyncEnumerable I2() + { + await Task.Yield(); + yield return 1; + } + } + """; + + var sourceComp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + sourceComp.VerifyDiagnostics(); + + var userComp = CreateCompilation("", references: [sourceComp.EmitToImageReference()]); + userComp.VerifyEmitDiagnostics(); + var testType = Assert.IsAssignableFrom(userComp.GetTypeByMetadataName("Test")); + + var i1 = testType.GetMethod("I1"); + Assert.False(i1.IsIterator); + Assert.False(i1.GetPublicSymbol().IsIterator); + + var i2 = testType.GetMethod("I2"); + Assert.False(i2.IsIterator); + Assert.False(i2.GetPublicSymbol().IsIterator); + } + + [Fact] + public void MethodJustReturnsEnumerable_NotIterator() + { + var source = """ + using System.Collections.Generic; + + class Test + { + IEnumerable I1() + { + return []; + } + + IAsyncEnumerable I2() + { + return default; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var i1 = comp.GetMember("Test.I1"); + Assert.False(i1.IsIterator); + Assert.False(i1.GetPublicSymbol().IsIterator); + + var i2 = comp.GetMember("Test.I2"); + Assert.False(i2.IsIterator); + Assert.False(i2.GetPublicSymbol().IsIterator); + } + [Fact] public void WrongYieldType() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index a5b2d45978332..3f571adf122cc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -2374,11 +2374,13 @@ public unsafe IEnumerable M4(int* a) var local = model.GetDeclaredSymbol(declaration).GetSymbol(); Assert.True(local.IsIterator); + Assert.True(local.GetPublicSymbol().IsIterator); Assert.Equal("System.Int32", local.IteratorElementTypeWithAnnotations.ToTestDisplayString()); model.GetOperation(declaration.Body); Assert.True(local.IsIterator); + Assert.True(local.GetPublicSymbol().IsIterator); Assert.Equal("System.Int32", local.IteratorElementTypeWithAnnotations.ToTestDisplayString()); comp.VerifyDiagnostics( @@ -10709,5 +10711,89 @@ public class C(string p) Diagnostic(ErrorCode.ERR_StaticLocalFunctionCannotCaptureVariable, "p").WithArguments("p").WithLocation(16, 42) ] : []); } + + [Fact] + public void SimpleIteratorLocalFunction() + { + var source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + + class C + { + void M() + { + #pragma warning disable 8321 // The local function 'X' is declared but never used + + IEnumerable I1() + { + yield return 1; + } + + async IAsyncEnumerable I2() + { + await Task.Yield(); + yield return 1; + } + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var syntaxTree = comp.SyntaxTrees.Single(); + var semanticModel = comp.GetSemanticModel(syntaxTree); + var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); + + var i1Syntax = localFunctionSyntaxes[0]; + IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + Assert.True(i1Symbol.IsIterator); + + var i2Syntax = localFunctionSyntaxes[1]; + IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + Assert.True(i2Symbol.IsIterator); + } + + [Fact] + public void LocalFunctionJustReturnsEnumerable_NotIterator() + { + var source = """ + using System.Collections.Generic; + + class C + { + void M() + { + #pragma warning disable 8321 // The local function 'X' is declared but never used + + IEnumerable I1() + { + return []; + } + + IAsyncEnumerable I2() + { + return default; + } + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var syntaxTree = comp.SyntaxTrees.Single(); + var semanticModel = comp.GetSemanticModel(syntaxTree); + var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); + + var i1Syntax = localFunctionSyntaxes[0]; + IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + Assert.False(i1Symbol.IsIterator); + + var i2Syntax = localFunctionSyntaxes[1]; + IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + Assert.False(i2Symbol.IsIterator); + } } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index d93281dfa3460..5c1c0095deb7c 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -3,6 +3,7 @@ Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitDifferenceOptions() -> void Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.get -> bool Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.init -> void +Microsoft.CodeAnalysis.IMethodSymbol.IsIterator.get -> bool static readonly Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.Default -> Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions Microsoft.CodeAnalysis.IEventSymbol.IsPartialDefinition.get -> bool Microsoft.CodeAnalysis.IEventSymbol.PartialDefinitionPart.get -> Microsoft.CodeAnalysis.IEventSymbol? diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index 74b62ca605838..471315eee86e4 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -293,5 +293,10 @@ public interface IMethodSymbol : ISymbol /// Returns a flag indicating whether this symbol has at least one applied/inherited conditional attribute. /// bool IsConditional { get; } + + /// + /// Returns if this method is a source method implemented as an iterator (either sync or async) + /// + bool IsIterator { get; } } } diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb index 215b7105d69f8..7acc4a1a228fc 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb @@ -133,7 +133,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' Source: Returns whether this method is an iterator; i.e., does it have the Iterator modifier? ''' Metadata: Returns False; methods from metadata cannot be an iterator. ''' - Public MustOverride ReadOnly Property IsIterator As Boolean + Public MustOverride ReadOnly Property IsIterator As Boolean Implements IMethodSymbol.IsIterator ''' ''' Indicates whether the accessor is marked with the 'init' modifier. diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb index 9eb87de985e30..6e5e0876ee2d4 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb @@ -2,7 +2,9 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities @@ -12,6 +14,75 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics Public Class IteratorTests Inherits FlowTestBase + + Public Sub BasicIterator() + Dim compilation = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim i = compilation.GetMember(Of MethodSymbol)("C.I") + Assert.True(i.IsIterator) + Assert.True(DirectCast(i, IMethodSymbol).IsIterator) + End Sub + + + Public Sub BasicIterator_Metadata() + Dim sourceComp = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim userComp = CreateCompilation("", references:={sourceComp.EmitToImageReference()}).VerifyDiagnostics() + Dim cMetadataType = Assert.IsAssignableFrom(Of PENamedTypeSymbol)(userComp.GetTypeByMetadataName("C")) + + Dim i = cMetadataType.GetMethod("I") + Assert.False(i.IsIterator) + Assert.False(DirectCast(i, IMethodSymbol).IsIterator) + End Sub + + + Public Sub Method_NotIterator() + Dim compilation = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim i = compilation.GetMember(Of MethodSymbol)("C.I") + Assert.False(i.IsIterator) + Assert.False(DirectCast(i, IMethodSymbol).IsIterator) + End Sub + Public Sub IteratorNoYields() Dim compilation = CreateCompilationWithMscorlib40AndVBRuntime( @@ -978,6 +1049,28 @@ End Class CompileAndVerify(compilation.WithOptions(TestOptions.ReleaseExe), expected) End Sub + + Public Sub SimpleIteratorProperty() + Dim compilation = CreateCompilation( + + +).VerifyDiagnostics() + + Dim [property] = compilation.GetMember(Of PropertySymbol)("C.P") + Assert.True([property].GetMethod.IsIterator) + Assert.True(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) + End Sub + @@ -1016,13 +1109,64 @@ End Class compilation.AssertTheseEmitDiagnostics() Dim [property] = compilation.GetMember(Of PropertySymbol)("A.P") Assert.True([property].GetMethod.IsIterator) + Assert.True(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) Assert.False([property].SetMethod.IsIterator) + Assert.False(DirectCast([property].SetMethod, IMethodSymbol).IsIterator) [property] = compilation.GetMember(Of PropertySymbol)("B.P") Assert.True([property].GetMethod.IsIterator) + Assert.True(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) Assert.False([property].SetMethod.IsIterator) + Assert.False(DirectCast([property].SetMethod, IMethodSymbol).IsIterator) CompileAndVerify(compilation, expectedOutput:="123") End Sub + + Public Sub IteratorProperty_Metadata() + Dim sourceComp = CreateCompilation( + + +).VerifyDiagnostics() + + Dim userComp = CreateCompilation("", references:={sourceComp.EmitToImageReference()}).VerifyDiagnostics() + Dim cMetadataType = Assert.IsAssignableFrom(Of PENamedTypeSymbol)(userComp.GetTypeByMetadataName("C")) + + Dim [property] = cMetadataType.GetProperty("P") + Assert.False([property].GetMethod.IsIterator) + Assert.False(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) + End Sub + + + Public Sub Property_NotIterator() + Dim compilation = CreateCompilation( + + +).VerifyDiagnostics() + + Dim [property] = compilation.GetMember(Of PropertySymbol)("C.P") + Assert.False([property].GetMethod.IsIterator) + Assert.False(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) + End Sub + Public Sub CompilerLoweringPreserveAttribute_01() Dim source1 = " diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb index be410c2a6445f..e2cfdfb80a5ee 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb @@ -5,6 +5,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics @@ -2491,5 +2492,54 @@ End Class CompileAndVerify(comp1, symbolValidator:=validate).VerifyDiagnostics() End Sub + + Public Sub IteratorLambda() + Dim compilation = CreateCompilation( + + +Imports System.Collections.Generic + +Class C + Sub M() + Dim lambda = Iterator Function() As IEnumerable(Of Integer) + Yield 1 + End Function + End Sub +End Class + +).VerifyDiagnostics() + + Dim syntaxTree = compilation.SyntaxTrees.Single() + Dim semanticModel = compilation.GetSemanticModel(syntaxTree) + Dim lambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Single() + Dim lambdaSymbolInfo = semanticModel.GetSymbolInfo(lambdaSyntax) + Dim lambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(lambdaSymbolInfo.Symbol) + Assert.True(lambdaMethod.IsIterator) + End Sub + + + Public Sub NotIteratorLambda() + Dim compilation = CreateCompilation( + + +Imports System.Collections.Generic + +Class C + Sub M() + Dim lambda = Function() As IEnumerable(Of Integer) + Return Nothing + End Function + End Sub +End Class + +).VerifyDiagnostics() + + Dim syntaxTree = compilation.SyntaxTrees.Single() + Dim semanticModel = compilation.GetSemanticModel(syntaxTree) + Dim lambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Single() + Dim lambdaSymbolInfo = semanticModel.GetSymbolInfo(lambdaSyntax) + Dim lambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(lambdaSymbolInfo.Symbol) + Assert.False(lambdaMethod.IsIterator) + End Sub End Class End Namespace diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs index e2238705948fc..ee971b2a1fa05 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs @@ -127,6 +127,8 @@ public IMethodSymbol ReduceExtensionMethod(ITypeSymbol receiverType) public bool IsConditional => _symbol.IsConditional; + public bool IsIterator => _symbol.IsIterator; + public SignatureCallingConvention CallingConvention => _symbol.CallingConvention; public ImmutableArray UnmanagedCallingConventionTypes => _symbol.UnmanagedCallingConventionTypes; diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt index 2e194d0b341b1..2092c158ffdf2 100644 --- a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt @@ -1241,6 +1241,7 @@ Microsoft.CodeAnalysis.IMethodSymbol.get_IsConditional Microsoft.CodeAnalysis.IMethodSymbol.get_IsExtensionMethod Microsoft.CodeAnalysis.IMethodSymbol.get_IsGenericMethod Microsoft.CodeAnalysis.IMethodSymbol.get_IsInitOnly +Microsoft.CodeAnalysis.IMethodSymbol.get_IsIterator Microsoft.CodeAnalysis.IMethodSymbol.get_IsPartialDefinition Microsoft.CodeAnalysis.IMethodSymbol.get_IsReadOnly Microsoft.CodeAnalysis.IMethodSymbol.get_IsVararg diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 45c058621bda5..a8617012efe32 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -104,6 +104,8 @@ public virtual ImmutableArray ReturnTypeCustomModifiers public bool IsConditional => false; + public bool IsIterator => false; + public SignatureCallingConvention CallingConvention => SignatureCallingConvention.Default; public ImmutableArray UnmanagedCallingConventionTypes => [];