From 4084702f055ca1b6910a7204e4d7f895804f8e2f Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 4 Jun 2025 12:44:11 +0300 Subject: [PATCH 01/14] Expose `IsIterator` as a public API --- .../Extensions/RewrittenMethodSymbol.cs | 3 +- .../CSharp/Portable/Symbols/MethodSymbol.cs | 3 +- .../Symbols/PublicModel/MethodSymbol.cs | 2 + ...LocalFunctionOrSourceMemberMethodSymbol.cs | 2 +- .../Test/Semantic/Semantics/IteratorTests.cs | 62 ++++++++++++++++ .../Core/Portable/PublicAPI.Unshipped.txt | 1 + .../Core/Portable/Symbols/IMethodSymbol.cs | 5 ++ .../Portable/Symbols/MethodSymbol.vb | 2 +- .../Test/Semantic/Semantics/IteratorTests.vb | 71 +++++++++++++++++++ 9 files changed, 145 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs index ff04573e94457..e1b798576dfee 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -59,7 +58,7 @@ internal override TypeWithAnnotations IteratorElementTypeWithAnnotations } } - internal override bool IsIterator + public override bool IsIterator { get { return _originalMethod.IsIterator; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 29d826d2b20e6..c7fc72ad9c7a1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -1085,7 +1084,7 @@ public sealed override bool HasUnsupportedMetadata #endregion - internal virtual bool IsIterator + public virtual bool IsIterator { get { 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/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs index b7a2d1a11cdb3..55263a9784a74 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs @@ -42,6 +42,6 @@ internal sealed override TypeWithAnnotations IteratorElementTypeWithAnnotations } } - internal sealed override bool IsIterator => _lazyIteratorElementType is object; + public sealed override bool IsIterator => _lazyIteratorElementType is object; } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index 69f12e7e3d7e2..76c1c67333d33 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 { @@ -61,6 +62,67 @@ 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.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); + + var i2 = testType.GetMethod("I2"); + Assert.False(i2.IsIterator); + } + [Fact] public void WrongYieldType() { 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 a7b7c16b01594..8930345d63fba 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.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics @@ -10,6 +12,51 @@ 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) + 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) + End Sub + Public Sub IteratorNoYields() Dim compilation = CreateCompilationWithMscorlib40AndVBRuntime( @@ -1021,6 +1068,30 @@ End Class 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) + End Sub + Public Sub CompilerLoweringPreserveAttribute_01() Dim source1 = " From 6f06fd89f679336eecd2deb53930c24d9412293e Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 4 Jun 2025 13:10:47 +0300 Subject: [PATCH 02/14] Fix workspace generated method symbol implementation --- .../Symbols/CodeGenerationAbstractMethodSymbol.cs | 2 ++ 1 file changed, 2 insertions(+) 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 => []; From 95267414f26ace60867523bd05cfa1e09ea70604 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 4 Jun 2025 15:28:17 +0300 Subject: [PATCH 03/14] Implement new property in yet another non-compiler-owned method symbol implementation --- .../AbstractMetadataAsSourceService.WrappedMethodSymbol.cs | 2 ++ 1 file changed, 2 insertions(+) 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; From 71ec9135c08e4388331cd3cb3edd5c7e67ed1c68 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 4 Jun 2025 16:12:22 +0300 Subject: [PATCH 04/14] SemanticSearch.ReferenceAssemblies --- .../ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt | 1 + 1 file changed, 1 insertion(+) 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 From cb20c159928c820490f38ce29ff472fdf976ea6d Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 7 Jun 2025 20:06:24 +0300 Subject: [PATCH 05/14] Verify public code path in C# --- .../CSharp/Test/Semantic/Semantics/IteratorTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index 76c1c67333d33..e266ea51db8dc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs @@ -36,12 +36,16 @@ IEnumerable I() var comp = CreateCompilation(text); var i = comp.GetMember("Test.I"); + var 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()); } @@ -84,6 +88,7 @@ async IAsyncEnumerable I() var i = comp.GetMember("Test.I"); Assert.True(i.IsIterator); + Assert.True(i.GetPublicSymbol().IsIterator); Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); } @@ -118,9 +123,11 @@ public async IAsyncEnumerable I2() 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] From 880b5578cad8f92d156dd9052d86794c261cd561 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 7 Jun 2025 21:34:01 +0300 Subject: [PATCH 06/14] Add negative tests --- .../Test/Semantic/Semantics/IteratorTests.cs | 32 ++++++++++++++ .../Test/Semantic/Semantics/IteratorTests.vb | 42 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index e266ea51db8dc..274ca2007b343 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs @@ -130,6 +130,38 @@ public async IAsyncEnumerable I2() 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/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb index 8930345d63fba..61e00127c6c6e 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb @@ -57,6 +57,27 @@ End Class Assert.False(i.IsIterator) End Sub + + Public Sub Method_NotIterator() + Dim compilation = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim i = compilation.GetMember(Of MethodSymbol)("C.I") + Assert.False(i.IsIterator) + End Sub + Public Sub IteratorNoYields() Dim compilation = CreateCompilationWithMscorlib40AndVBRuntime( @@ -1092,6 +1113,27 @@ End Class Assert.False([property].GetMethod.IsIterator) End Sub + + Public Sub Property_NotIterator() + Dim compilation = CreateCompilation( + + +).VerifyDiagnostics() + + Dim [property] = compilation.GetMember(Of PropertySymbol)("C.P") + Assert.False([property].GetMethod.IsIterator) + End Sub + Public Sub CompilerLoweringPreserveAttribute_01() Dim source1 = " From 2f7dc66c9f3825b72c94627d10f7520b3d0b929a Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 7 Jun 2025 21:34:54 +0300 Subject: [PATCH 07/14] Add C# local function tests --- .../Semantic/Semantics/LocalFunctionTests.cs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index a5b2d45978332..acda457128a87 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,97 @@ 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() + { + IEnumerable I1() + { + yield return 1; + } + + async IAsyncEnumerable I2() + { + await Task.Yield(); + yield return 1; + } + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics( + // (8,26): warning CS8321: The local function 'I1' is declared but never used + // IEnumerable I1() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I1").WithArguments("I1").WithLocation(8, 26), + // (13,37): warning CS8321: The local function 'I2' is declared but never used + // async IAsyncEnumerable I2() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I2").WithArguments("I2").WithLocation(13, 37)); + + var syntaxTree = comp.SyntaxTrees.Single(); + var semanticModel = comp.GetSemanticModel(syntaxTree); + var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); + + var i1Syntax = localFunctionSyntaxes[0]; + var i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + Assert.True(i1Symbol.IsIterator); + + var i2Syntax = localFunctionSyntaxes[1]; + var i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + Assert.True(i2Symbol.IsIterator); + } + + [Fact] + public void LocalFunctionJustReturnsEnumerable_NotIterator() + { + var source = """ + using System.Collections.Generic; + + class C + { + void M() + { + IEnumerable I1() + { + return []; + } + + IAsyncEnumerable I2() + { + return default; + } + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics( + // (7,26): warning CS8321: The local function 'I1' is declared but never used + // IEnumerable I1() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I1").WithArguments("I1").WithLocation(7, 26), + // (12,31): warning CS8321: The local function 'I2' is declared but never used + // IAsyncEnumerable I2() + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I2").WithArguments("I2").WithLocation(12, 31)); + + var syntaxTree = comp.SyntaxTrees.Single(); + var semanticModel = comp.GetSemanticModel(syntaxTree); + var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); + + var i1Syntax = localFunctionSyntaxes[0]; + var i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + Assert.False(i1Symbol.IsIterator); + + var i2Syntax = localFunctionSyntaxes[1]; + var i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + Assert.False(i2Symbol.IsIterator); + } } } From ef856210d6a1c6c3738a3277914e7650a1bbc439 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 7 Jun 2025 22:00:00 +0300 Subject: [PATCH 08/14] Test VB lambda iterators --- .../Test/Semantic/Semantics/LambdaTests.vb | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb index be410c2a6445f..5b792e62c86d7 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,58 @@ 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 = Function() As IEnumerable(Of Integer) + Return Iterator Function() As IEnumerable(Of Integer) + Yield 1 + End Function() + End Function + End Sub +End Class + +).VerifyDiagnostics() + + Dim syntaxTree = compilation.SyntaxTrees.Single() + Dim semanticModel = compilation.GetSemanticModel(syntaxTree) + Dim innerLambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Skip(1).Take(1).Single() + Dim innerLambdaSymbolInfo = semanticModel.GetSymbolInfo(innerLambdaSyntax) + Dim innerLambdaMethod = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) + Assert.True(innerLambdaMethod.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 Function() As IEnumerable(Of Integer) + Return Nothing + End Function() + End Function + End Sub +End Class + +).VerifyDiagnostics() + + Dim syntaxTree = compilation.SyntaxTrees.Single() + Dim semanticModel = compilation.GetSemanticModel(syntaxTree) + Dim innerLambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Skip(1).Take(1).Single() + Dim innerLambdaSymbolInfo = semanticModel.GetSymbolInfo(innerLambdaSyntax) + Dim innerLambdaMethod = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) + Assert.False(innerLambdaMethod.IsIterator) + End Sub End Class End Namespace From 8b3e5468d71fbc3e51afbe37a86910c5bf2f00d0 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 9 Jun 2025 20:35:50 +0300 Subject: [PATCH 09/14] Revert accessibility change --- .../Portable/Symbols/Extensions/RewrittenMethodSymbol.cs | 3 ++- src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs | 3 ++- .../Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs index e1b798576dfee..ff04573e94457 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -58,7 +59,7 @@ internal override TypeWithAnnotations IteratorElementTypeWithAnnotations } } - public override bool IsIterator + internal override bool IsIterator { get { return _originalMethod.IsIterator; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 6e6c9a83a9ffe..7a54b67c0588e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -1086,7 +1087,7 @@ public sealed override bool HasUnsupportedMetadata #endregion - public virtual bool IsIterator + internal virtual bool IsIterator { get { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs index 55263a9784a74..b7a2d1a11cdb3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionOrSourceMemberMethodSymbol.cs @@ -42,6 +42,6 @@ internal sealed override TypeWithAnnotations IteratorElementTypeWithAnnotations } } - public sealed override bool IsIterator => _lazyIteratorElementType is object; + internal sealed override bool IsIterator => _lazyIteratorElementType is object; } } From 1c75ad6c2133decb6fbabff95078da31cbf1eaed Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 9 Jun 2025 20:43:18 +0300 Subject: [PATCH 10/14] Disable warnings --- .../Semantic/Semantics/LocalFunctionTests.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index acda457128a87..c1660bc9239ed 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -10723,6 +10723,8 @@ class C { void M() { + #pragma warning disable 8321 // The local function 'X' is declared but never used + IEnumerable I1() { yield return 1; @@ -10738,13 +10740,7 @@ async IAsyncEnumerable I2() """; var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); - comp.VerifyDiagnostics( - // (8,26): warning CS8321: The local function 'I1' is declared but never used - // IEnumerable I1() - Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I1").WithArguments("I1").WithLocation(8, 26), - // (13,37): warning CS8321: The local function 'I2' is declared but never used - // async IAsyncEnumerable I2() - Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I2").WithArguments("I2").WithLocation(13, 37)); + comp.VerifyDiagnostics(); var syntaxTree = comp.SyntaxTrees.Single(); var semanticModel = comp.GetSemanticModel(syntaxTree); @@ -10769,6 +10765,8 @@ class C { void M() { + #pragma warning disable 8321 // The local function 'X' is declared but never used + IEnumerable I1() { return []; @@ -10783,13 +10781,7 @@ IAsyncEnumerable I2() """; var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); - comp.VerifyDiagnostics( - // (7,26): warning CS8321: The local function 'I1' is declared but never used - // IEnumerable I1() - Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I1").WithArguments("I1").WithLocation(7, 26), - // (12,31): warning CS8321: The local function 'I2' is declared but never used - // IAsyncEnumerable I2() - Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "I2").WithArguments("I2").WithLocation(12, 31)); + comp.VerifyDiagnostics(); var syntaxTree = comp.SyntaxTrees.Single(); var semanticModel = comp.GetSemanticModel(syntaxTree); From 874156e82adf115aa2be1454780f81480c8157bd Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 9 Jun 2025 20:47:07 +0300 Subject: [PATCH 11/14] Use explicit type --- .../CSharp/Test/Semantic/Semantics/IteratorTests.cs | 2 +- .../CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs | 8 ++++---- .../VisualBasic/Test/Semantic/Semantics/LambdaTests.vb | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index 274ca2007b343..0692049f45402 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs @@ -36,7 +36,7 @@ IEnumerable I() var comp = CreateCompilation(text); var i = comp.GetMember("Test.I"); - var publicI = i.GetPublicSymbol(); + IMethodSymbol publicI = i.GetPublicSymbol(); Assert.True(i.IsIterator); Assert.True(publicI.IsIterator); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index c1660bc9239ed..3f571adf122cc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -10747,11 +10747,11 @@ async IAsyncEnumerable I2() var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); var i1Syntax = localFunctionSyntaxes[0]; - var i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); Assert.True(i1Symbol.IsIterator); var i2Syntax = localFunctionSyntaxes[1]; - var i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); Assert.True(i2Symbol.IsIterator); } @@ -10788,11 +10788,11 @@ IAsyncEnumerable I2() var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); var i1Syntax = localFunctionSyntaxes[0]; - var i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); Assert.False(i1Symbol.IsIterator); var i2Syntax = localFunctionSyntaxes[1]; - var i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); Assert.False(i2Symbol.IsIterator); } } diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb index 5b792e62c86d7..610e027e3c653 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb @@ -2515,7 +2515,7 @@ End Class Dim semanticModel = compilation.GetSemanticModel(syntaxTree) Dim innerLambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Skip(1).Take(1).Single() Dim innerLambdaSymbolInfo = semanticModel.GetSymbolInfo(innerLambdaSyntax) - Dim innerLambdaMethod = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) + Dim innerLambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) Assert.True(innerLambdaMethod.IsIterator) End Sub @@ -2542,7 +2542,7 @@ End Class Dim semanticModel = compilation.GetSemanticModel(syntaxTree) Dim innerLambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Skip(1).Take(1).Single() Dim innerLambdaSymbolInfo = semanticModel.GetSymbolInfo(innerLambdaSyntax) - Dim innerLambdaMethod = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) + Dim innerLambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) Assert.False(innerLambdaMethod.IsIterator) End Sub End Class From 117ebe22b20fec0928423f19a9dc8c1e95b3015e Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 9 Jun 2025 20:58:56 +0300 Subject: [PATCH 12/14] Simplify lambda tests --- .../Test/Semantic/Semantics/LambdaTests.vb | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb index 610e027e3c653..e2cfdfb80a5ee 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb @@ -2501,10 +2501,8 @@ Imports System.Collections.Generic Class C Sub M() - Dim lambda = Function() As IEnumerable(Of Integer) - Return Iterator Function() As IEnumerable(Of Integer) - Yield 1 - End Function() + Dim lambda = Iterator Function() As IEnumerable(Of Integer) + Yield 1 End Function End Sub End Class @@ -2513,10 +2511,10 @@ End Class Dim syntaxTree = compilation.SyntaxTrees.Single() Dim semanticModel = compilation.GetSemanticModel(syntaxTree) - Dim innerLambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Skip(1).Take(1).Single() - Dim innerLambdaSymbolInfo = semanticModel.GetSymbolInfo(innerLambdaSyntax) - Dim innerLambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) - Assert.True(innerLambdaMethod.IsIterator) + 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 @@ -2529,9 +2527,7 @@ Imports System.Collections.Generic Class C Sub M() Dim lambda = Function() As IEnumerable(Of Integer) - Return Function() As IEnumerable(Of Integer) - Return Nothing - End Function() + Return Nothing End Function End Sub End Class @@ -2540,10 +2536,10 @@ End Class Dim syntaxTree = compilation.SyntaxTrees.Single() Dim semanticModel = compilation.GetSemanticModel(syntaxTree) - Dim innerLambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Skip(1).Take(1).Single() - Dim innerLambdaSymbolInfo = semanticModel.GetSymbolInfo(innerLambdaSyntax) - Dim innerLambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(innerLambdaSymbolInfo.Symbol) - Assert.False(innerLambdaMethod.IsIterator) + 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 From fdfc7b45fa844c67370e8d3302b7f8deae3bd446 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 9 Jun 2025 21:10:54 +0300 Subject: [PATCH 13/14] Test interface property --- .../VisualBasic/Test/Semantic/Semantics/IteratorTests.vb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb index 1687a882fe45c..e74013f910605 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb @@ -33,6 +33,7 @@ End Class Dim i = compilation.GetMember(Of MethodSymbol)("C.I") Assert.True(i.IsIterator) + Assert.True(DirectCast(i, IMethodSymbol).IsIterator) End Sub @@ -57,6 +58,7 @@ End Class Dim i = cMetadataType.GetMethod("I") Assert.False(i.IsIterator) + Assert.False(DirectCast(i, IMethodSymbol).IsIterator) End Sub @@ -78,6 +80,7 @@ End Class Dim i = compilation.GetMember(Of MethodSymbol)("C.I") Assert.False(i.IsIterator) + Assert.False(DirectCast(i, IMethodSymbol).IsIterator) End Sub @@ -1084,10 +1087,14 @@ 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 @@ -1113,6 +1120,7 @@ End Class Dim [property] = cMetadataType.GetProperty("P") Assert.False([property].GetMethod.IsIterator) + Assert.False(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) End Sub @@ -1134,6 +1142,7 @@ End Class Dim [property] = compilation.GetMember(Of PropertySymbol)("C.P") Assert.False([property].GetMethod.IsIterator) + Assert.False(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) End Sub From 0f2c7da1dd54ab24aad0d7c65f0ddd249a585b75 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 9 Jun 2025 21:11:03 +0300 Subject: [PATCH 14/14] Add additional VB case --- .../Test/Semantic/Semantics/IteratorTests.vb | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb index e74013f910605..6e5e0876ee2d4 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb @@ -1049,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 +