Skip to content

Commit 1d0d8a5

Browse files
authored
Expose IsIterator as a public API (#78813)
* Expose `IsIterator` as a public API * Fix workspace generated method symbol implementation * Implement new property in yet another non-compiler-owned method symbol implementation * SemanticSearch.ReferenceAssemblies * Verify public code path in C# * Add negative tests * Add C# local function tests * Test VB lambda iterators * Revert accessibility change * Disable warnings * Use explicit type * Simplify lambda tests * Test interface property * Add additional VB case
1 parent fb72d38 commit 1d0d8a5

File tree

11 files changed

+395
-1
lines changed

11 files changed

+395
-1
lines changed

src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ INamedTypeSymbol IMethodSymbol.AssociatedAnonymousDelegate
328328

329329
bool IMethodSymbol.IsConditional => _underlying.IsConditional;
330330

331+
bool IMethodSymbol.IsIterator => _underlying.IsIterator;
332+
331333
DllImportData IMethodSymbol.GetDllImportData() => _underlying.GetDllImportData();
332334

333335
#region ISymbol Members

src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.CodeAnalysis.CSharp.Symbols;
1414
using Microsoft.CodeAnalysis.Test.Utilities;
1515
using System.Linq;
16+
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
1617

1718
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics
1819
{
@@ -35,12 +36,16 @@ IEnumerable<int> I()
3536
var comp = CreateCompilation(text);
3637

3738
var i = comp.GetMember<MethodSymbol>("Test.I");
39+
IMethodSymbol publicI = i.GetPublicSymbol();
40+
3841
Assert.True(i.IsIterator);
42+
Assert.True(publicI.IsIterator);
3943
Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString());
4044

4145
comp.VerifyDiagnostics();
4246

4347
Assert.True(i.IsIterator);
48+
Assert.True(publicI.IsIterator);
4449
Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString());
4550
}
4651

@@ -61,6 +66,102 @@ IEnumerable<int> I()
6166
comp.VerifyDiagnostics();
6267
}
6368

69+
[Fact]
70+
public void BasicIterators_Async()
71+
{
72+
var source = """
73+
using System.Collections.Generic;
74+
using System.Threading.Tasks;
75+
76+
class Test
77+
{
78+
async IAsyncEnumerable<int> I()
79+
{
80+
await Task.Yield();
81+
yield return 1;
82+
}
83+
}
84+
""";
85+
86+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
87+
comp.VerifyDiagnostics();
88+
89+
var i = comp.GetMember<MethodSymbol>("Test.I");
90+
Assert.True(i.IsIterator);
91+
Assert.True(i.GetPublicSymbol().IsIterator);
92+
Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString());
93+
}
94+
95+
[Fact]
96+
public void BasicIterators_Metadata()
97+
{
98+
var source = """
99+
using System.Collections.Generic;
100+
using System.Threading.Tasks;
101+
102+
public class Test
103+
{
104+
public IEnumerable<int> I1()
105+
{
106+
yield return 1;
107+
}
108+
109+
public async IAsyncEnumerable<int> I2()
110+
{
111+
await Task.Yield();
112+
yield return 1;
113+
}
114+
}
115+
""";
116+
117+
var sourceComp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
118+
sourceComp.VerifyDiagnostics();
119+
120+
var userComp = CreateCompilation("", references: [sourceComp.EmitToImageReference()]);
121+
userComp.VerifyEmitDiagnostics();
122+
var testType = Assert.IsAssignableFrom<PENamedTypeSymbol>(userComp.GetTypeByMetadataName("Test"));
123+
124+
var i1 = testType.GetMethod("I1");
125+
Assert.False(i1.IsIterator);
126+
Assert.False(i1.GetPublicSymbol().IsIterator);
127+
128+
var i2 = testType.GetMethod("I2");
129+
Assert.False(i2.IsIterator);
130+
Assert.False(i2.GetPublicSymbol().IsIterator);
131+
}
132+
133+
[Fact]
134+
public void MethodJustReturnsEnumerable_NotIterator()
135+
{
136+
var source = """
137+
using System.Collections.Generic;
138+
139+
class Test
140+
{
141+
IEnumerable<int> I1()
142+
{
143+
return [];
144+
}
145+
146+
IAsyncEnumerable<int> I2()
147+
{
148+
return default;
149+
}
150+
}
151+
""";
152+
153+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
154+
comp.VerifyDiagnostics();
155+
156+
var i1 = comp.GetMember<MethodSymbol>("Test.I1");
157+
Assert.False(i1.IsIterator);
158+
Assert.False(i1.GetPublicSymbol().IsIterator);
159+
160+
var i2 = comp.GetMember<MethodSymbol>("Test.I2");
161+
Assert.False(i2.IsIterator);
162+
Assert.False(i2.GetPublicSymbol().IsIterator);
163+
}
164+
64165
[Fact]
65166
public void WrongYieldType()
66167
{

src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,11 +2374,13 @@ public unsafe IEnumerable<int> M4(int* a)
23742374
var local = model.GetDeclaredSymbol(declaration).GetSymbol<MethodSymbol>();
23752375

23762376
Assert.True(local.IsIterator);
2377+
Assert.True(local.GetPublicSymbol().IsIterator);
23772378
Assert.Equal("System.Int32", local.IteratorElementTypeWithAnnotations.ToTestDisplayString());
23782379

23792380
model.GetOperation(declaration.Body);
23802381

23812382
Assert.True(local.IsIterator);
2383+
Assert.True(local.GetPublicSymbol().IsIterator);
23822384
Assert.Equal("System.Int32", local.IteratorElementTypeWithAnnotations.ToTestDisplayString());
23832385

23842386
comp.VerifyDiagnostics(
@@ -10709,5 +10711,89 @@ public class C(string p)
1070910711
Diagnostic(ErrorCode.ERR_StaticLocalFunctionCannotCaptureVariable, "p").WithArguments("p").WithLocation(16, 42)
1071010712
] : []);
1071110713
}
10714+
10715+
[Fact]
10716+
public void SimpleIteratorLocalFunction()
10717+
{
10718+
var source = """
10719+
using System.Collections.Generic;
10720+
using System.Threading.Tasks;
10721+
10722+
class C
10723+
{
10724+
void M()
10725+
{
10726+
#pragma warning disable 8321 // The local function 'X' is declared but never used
10727+
10728+
IEnumerable<int> I1()
10729+
{
10730+
yield return 1;
10731+
}
10732+
10733+
async IAsyncEnumerable<int> I2()
10734+
{
10735+
await Task.Yield();
10736+
yield return 1;
10737+
}
10738+
}
10739+
}
10740+
""";
10741+
10742+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
10743+
comp.VerifyDiagnostics();
10744+
10745+
var syntaxTree = comp.SyntaxTrees.Single();
10746+
var semanticModel = comp.GetSemanticModel(syntaxTree);
10747+
var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType<LocalFunctionStatementSyntax>().ToArray();
10748+
10749+
var i1Syntax = localFunctionSyntaxes[0];
10750+
IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax);
10751+
Assert.True(i1Symbol.IsIterator);
10752+
10753+
var i2Syntax = localFunctionSyntaxes[1];
10754+
IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax);
10755+
Assert.True(i2Symbol.IsIterator);
10756+
}
10757+
10758+
[Fact]
10759+
public void LocalFunctionJustReturnsEnumerable_NotIterator()
10760+
{
10761+
var source = """
10762+
using System.Collections.Generic;
10763+
10764+
class C
10765+
{
10766+
void M()
10767+
{
10768+
#pragma warning disable 8321 // The local function 'X' is declared but never used
10769+
10770+
IEnumerable<int> I1()
10771+
{
10772+
return [];
10773+
}
10774+
10775+
IAsyncEnumerable<int> I2()
10776+
{
10777+
return default;
10778+
}
10779+
}
10780+
}
10781+
""";
10782+
10783+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
10784+
comp.VerifyDiagnostics();
10785+
10786+
var syntaxTree = comp.SyntaxTrees.Single();
10787+
var semanticModel = comp.GetSemanticModel(syntaxTree);
10788+
var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType<LocalFunctionStatementSyntax>().ToArray();
10789+
10790+
var i1Syntax = localFunctionSyntaxes[0];
10791+
IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax);
10792+
Assert.False(i1Symbol.IsIterator);
10793+
10794+
var i2Syntax = localFunctionSyntaxes[1];
10795+
IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax);
10796+
Assert.False(i2Symbol.IsIterator);
10797+
}
1071210798
}
1071310799
}

src/Compilers/Core/Portable/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions
1212
Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitDifferenceOptions() -> void
1313
Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.get -> bool
1414
Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.init -> void
15+
Microsoft.CodeAnalysis.IMethodSymbol.IsIterator.get -> bool
1516
static readonly Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.Default -> Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions
1617
Microsoft.CodeAnalysis.IEventSymbol.IsPartialDefinition.get -> bool
1718
Microsoft.CodeAnalysis.IEventSymbol.PartialDefinitionPart.get -> Microsoft.CodeAnalysis.IEventSymbol?

src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,5 +293,10 @@ public interface IMethodSymbol : ISymbol
293293
/// Returns a flag indicating whether this symbol has at least one applied/inherited conditional attribute.
294294
/// </summary>
295295
bool IsConditional { get; }
296+
297+
/// <summary>
298+
/// Returns <see langword="true"/> if this method is a source method implemented as an iterator (either sync or async)
299+
/// </summary>
300+
bool IsIterator { get; }
296301
}
297302
}

src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
133133
''' Source: Returns whether this method is an iterator; i.e., does it have the Iterator modifier?
134134
''' Metadata: Returns False; methods from metadata cannot be an iterator.
135135
''' </summary>
136-
Public MustOverride ReadOnly Property IsIterator As Boolean
136+
Public MustOverride ReadOnly Property IsIterator As Boolean Implements IMethodSymbol.IsIterator
137137

138138
''' <summary>
139139
''' Indicates whether the accessor is marked with the 'init' modifier.

0 commit comments

Comments
 (0)