Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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 @@ -4,7 +4,6 @@

using System.Collections.Immutable;
using System.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
Expand Down Expand Up @@ -59,7 +58,7 @@ internal override TypeWithAnnotations IteratorElementTypeWithAnnotations
}
}

internal override bool IsIterator
public override bool IsIterator
{
get { return _originalMethod.IsIterator; }
}
Expand Down
3 changes: 1 addition & 2 deletions src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -1087,7 +1086,7 @@ public sealed override bool HasUnsupportedMetadata

#endregion

internal virtual bool IsIterator
public virtual bool IsIterator
{
get
{
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
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");
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());
}

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,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<int> I1()
{
yield return 1;
}

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

IAsyncEnumerable<int> 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<int> 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<int> 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<LocalFunctionStatementSyntax>().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);
}
}
}
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