Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
101 changes: 101 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -35,12 +36,16 @@ IEnumerable<int> I()
var comp = CreateCompilation(text);

var i = comp.GetMember<MethodSymbol>("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());
}

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

[Fact]
public void BasicIterators_Async()
{
var source = """
using System.Collections.Generic;
using System.Threading.Tasks;

class Test
{
async IAsyncEnumerable<int> I()
{
await Task.Yield();
yield return 1;
}
}
""";

var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
comp.VerifyDiagnostics();

var i = comp.GetMember<MethodSymbol>("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<int> I1()
{
yield return 1;
}

public async IAsyncEnumerable<int> 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<PENamedTypeSymbol>(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<int> I1()
{
return [];
}

IAsyncEnumerable<int> I2()
{
return default;
}
}
""";

var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60);
comp.VerifyDiagnostics();

var i1 = comp.GetMember<MethodSymbol>("Test.I1");
Assert.False(i1.IsIterator);
Assert.False(i1.GetPublicSymbol().IsIterator);

var i2 = comp.GetMember<MethodSymbol>("Test.I2");
Assert.False(i2.IsIterator);
Assert.False(i2.GetPublicSymbol().IsIterator);
}

[Fact]
public void WrongYieldType()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2374,11 +2374,13 @@ public unsafe IEnumerable<int> M4(int* a)
var local = model.GetDeclaredSymbol(declaration).GetSymbol<MethodSymbol>();

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(
Expand Down Expand Up @@ -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<int> I1()
{
yield return 1;
}

async IAsyncEnumerable<int> 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<LocalFunctionStatementSyntax>().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<int> I1()
{
return [];
}

IAsyncEnumerable<int> 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<LocalFunctionStatementSyntax>().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);
}
}
}
1 change: 1 addition & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,10 @@ public interface IMethodSymbol : ISymbol
/// Returns a flag indicating whether this symbol has at least one applied/inherited conditional attribute.
/// </summary>
bool IsConditional { get; }

/// <summary>
/// Returns <see langword="true"/> if this method is a source method implemented as an iterator (either sync or async)
/// </summary>
bool IsIterator { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
''' </summary>
Public MustOverride ReadOnly Property IsIterator As Boolean
Public MustOverride ReadOnly Property IsIterator As Boolean Implements IMethodSymbol.IsIterator

''' <summary>
''' Indicates whether the accessor is marked with the 'init' modifier.
Expand Down
Loading