|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// A loop binder that (1) knows how to bind foreach loops and (2) has the foreach iteration variable in scope.
/// </summary>
/// <remarks>
/// This binder produces BoundForEachStatements. The lowering described in the spec is performed in ControlFlowRewriter.
/// </remarks>
internal sealed class ForEachLoopBinder : LoopBinder
{
private readonly CommonForEachStatementSyntax _syntax;
private SourceLocalSymbol IterationVariable
{
get
{
return (_syntax.Kind() == SyntaxKind.ForEachStatement) ? (SourceLocalSymbol)this.Locals[0] : null;
}
}
private bool IsAsync
=> _syntax.AwaitKeyword != default;
public ForEachLoopBinder(Binder enclosing, CommonForEachStatementSyntax syntax)
: base(enclosing)
{
Debug.Assert(syntax != null);
_syntax = syntax;
}
internal override ImmutableArray<LocalSymbol> GetDeclaredLocalsForScope(SyntaxNode scopeDesignator)
{
if (_syntax == scopeDesignator)
{
return this.Locals;
}
throw ExceptionUtilities.Unreachable();
}
internal override ImmutableArray<LocalFunctionSymbol> GetDeclaredLocalFunctionsForScope(CSharpSyntaxNode scopeDesignator)
{
throw ExceptionUtilities.Unreachable();
}
internal override SyntaxNode ScopeDesignator
{
get
{
return _syntax;
}
}
protected override ImmutableArray<LocalSymbol> BuildLocals()
{
switch (_syntax.Kind())
{
case SyntaxKind.ForEachVariableStatement:
{
var syntax = (ForEachVariableStatementSyntax)_syntax;
var locals = ArrayBuilder<LocalSymbol>.GetInstance();
CollectLocalsFromDeconstruction(
syntax.Variable,
LocalDeclarationKind.ForEachIterationVariable,
locals,
syntax);
return locals.ToImmutableAndFree();
}
case SyntaxKind.ForEachStatement:
{
var syntax = (ForEachStatementSyntax)_syntax;
var iterationVariable = SourceLocalSymbol.MakeForeachLocal(
(MethodSymbol)this.ContainingMemberOrLambda,
this,
syntax.Type,
syntax.Identifier,
syntax.Expression);
return ImmutableArray.Create<LocalSymbol>(iterationVariable);
}
default:
throw ExceptionUtilities.UnexpectedValue(_syntax.Kind());
}
}
internal void CollectLocalsFromDeconstruction(
ExpressionSyntax declaration,
LocalDeclarationKind kind,
ArrayBuilder<LocalSymbol> locals,
SyntaxNode deconstructionStatement,
Binder enclosingBinderOpt = null)
{
switch (declaration.Kind())
{
case SyntaxKind.TupleExpression:
{
var tuple = (TupleExpressionSyntax)declaration;
foreach (var arg in tuple.Arguments)
{
CollectLocalsFromDeconstruction(arg.Expression, kind, locals, deconstructionStatement, enclosingBinderOpt);
}
break;
}
case SyntaxKind.DeclarationExpression:
{
var declarationExpression = (DeclarationExpressionSyntax)declaration;
CollectLocalsFromDeconstruction(
declarationExpression.Designation, declarationExpression.Type,
kind, locals, deconstructionStatement, enclosingBinderOpt);
break;
}
case SyntaxKind.IdentifierName:
break;
default:
// In broken code, we can have an arbitrary expression here. Collect its expression variables.
ExpressionVariableFinder.FindExpressionVariables(this, locals, declaration);
break;
}
}
internal void CollectLocalsFromDeconstruction(
VariableDesignationSyntax designation,
TypeSyntax closestTypeSyntax,
LocalDeclarationKind kind,
ArrayBuilder<LocalSymbol> locals,
SyntaxNode deconstructionStatement,
Binder enclosingBinderOpt)
{
switch (designation.Kind())
{
case SyntaxKind.SingleVariableDesignation:
{
var single = (SingleVariableDesignationSyntax)designation;
SourceLocalSymbol localSymbol = SourceLocalSymbol.MakeDeconstructionLocal(
this.ContainingMemberOrLambda,
this,
enclosingBinderOpt ?? this,
closestTypeSyntax,
single.Identifier,
kind,
deconstructionStatement);
locals.Add(localSymbol);
break;
}
case SyntaxKind.ParenthesizedVariableDesignation:
{
var tuple = (ParenthesizedVariableDesignationSyntax)designation;
foreach (var d in tuple.Variables)
{
CollectLocalsFromDeconstruction(d, closestTypeSyntax, kind, locals, deconstructionStatement, enclosingBinderOpt);
}
break;
}
case SyntaxKind.DiscardDesignation:
break;
default:
throw ExceptionUtilities.UnexpectedValue(designation.Kind());
}
}
/// <summary>
/// Bind the ForEachStatementSyntax at the root of this binder.
/// </summary>
internal override BoundStatement BindForEachParts(BindingDiagnosticBag diagnostics, Binder originalBinder)
{
BoundForEachStatement result = BindForEachPartsWorker(diagnostics, originalBinder);
return result;
}
/// <summary>
/// Like BindForEachParts, but only bind the deconstruction part of the foreach, for purpose of inferring the types of the declared locals.
/// </summary>
internal override BoundStatement BindForEachDeconstruction(BindingDiagnosticBag diagnostics, Binder originalBinder)
{
// Use the right binder to avoid seeing iteration variable
BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics);
TypeWithAnnotations inferredType;
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(_syntax, _syntax.Expression, ref collectionExpr, isAsync: IsAsync, isSpread: false, diagnostics, out inferredType, builder: out _);
ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable;
// Tracking narrowest safe-to-escape scope by default, the proper val escape will be set when doing full binding of the foreach statement
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, isDiscardExpression: false, inferredType.Type ?? CreateErrorType("var"));
DeclarationExpressionSyntax declaration = null;
ExpressionSyntax expression = null;
BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction(
variables,
variables,
right: _syntax.Expression,
diagnostics: diagnostics,
rightPlaceholder: valuePlaceholder,
declaration: ref declaration,
expression: ref expression);
return new BoundExpressionStatement(_syntax, deconstruction);
}
private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagnostics, Binder originalBinder)
{
if (IsAsync)
{
CheckFeatureAvailability(_syntax.AwaitKeyword, MessageID.IDS_FeatureAsyncStreams, diagnostics);
}
// Use the right binder to avoid seeing iteration variable
BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics);
ForEachEnumeratorInfo.Builder builder;
TypeWithAnnotations inferredType;
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(_syntax, _syntax.Expression, ref collectionExpr, isAsync: IsAsync, isSpread: false, diagnostics, out inferredType, out builder);
// These occur when special types are missing or malformed, or the patterns are incompletely implemented.
hasErrors |= builder.IsIncomplete;
BoundAwaitableInfo awaitInfo = null;
MethodSymbol getEnumeratorMethod = builder.GetEnumeratorInfo?.Method;
if (getEnumeratorMethod != null)
{
originalBinder.CheckImplicitThisCopyInReadOnlyMember(collectionExpr, getEnumeratorMethod, diagnostics);
if (getEnumeratorMethod.IsExtensionMethod && !hasErrors)
{
var messageId = IsAsync ? MessageID.IDS_FeatureExtensionGetAsyncEnumerator : MessageID.IDS_FeatureExtensionGetEnumerator;
messageId.CheckFeatureAvailability(diagnostics, Compilation, collectionExpr.Syntax.Location);
if (getEnumeratorMethod.ParameterRefKinds is { IsDefault: false } refKinds && refKinds[0] == RefKind.Ref)
{
Error(diagnostics, ErrorCode.ERR_RefLvalueExpected, collectionExpr.Syntax);
hasErrors = true;
}
}
}
if (IsAsync)
{
var expr = _syntax.Expression;
ReportBadAwaitDiagnostics(_syntax.AwaitKeyword, diagnostics, ref hasErrors);
var placeholder = new BoundAwaitableValuePlaceholder(expr, builder.MoveNextInfo?.Method.ReturnType ?? CreateErrorType());
awaitInfo = BindAwaitInfo(placeholder, expr, diagnostics, ref hasErrors);
if (!hasErrors && awaitInfo.GetResult?.ReturnType.SpecialType != SpecialType.System_Boolean)
{
diagnostics.Add(ErrorCode.ERR_BadGetAsyncEnumerator, expr.Location, getEnumeratorMethod.ReturnTypeWithAnnotations, getEnumeratorMethod);
hasErrors = true;
}
}
TypeWithAnnotations iterationVariableType;
BoundTypeExpression boundIterationVariableType;
bool hasNameConflicts = false;
BoundForEachDeconstructStep deconstructStep = null;
BoundExpression iterationErrorExpression = null;
switch (_syntax.Kind())
{
case SyntaxKind.ForEachStatement:
{
var node = (ForEachStatementSyntax)_syntax;
// Check for local variable conflicts in the *enclosing* binder; obviously the *current*
// binder has a local that matches!
hasNameConflicts = originalBinder.ValidateDeclarationNameConflictsInScope(IterationVariable, diagnostics);
// If the type in syntax is "var", then the type should be set explicitly so that the
// Type property doesn't fail.
TypeSyntax typeSyntax = node.Type;
if (typeSyntax is ScopedTypeSyntax scopedType)
{
// Check for support for 'scoped'.
ModifierUtils.CheckScopedModifierAvailability(typeSyntax, scopedType.ScopedKeyword, diagnostics);
typeSyntax = scopedType.Type;
}
if (typeSyntax is RefTypeSyntax refType)
{
MessageID.IDS_FeatureRefForEach.CheckFeatureAvailability(diagnostics, typeSyntax);
typeSyntax = refType.Type;
}
bool isVar;
AliasSymbol alias;
TypeWithAnnotations declType = BindTypeOrVarKeyword(typeSyntax, diagnostics, out isVar, out alias);
if (isVar)
{
declType = inferredType.HasType ? inferredType : TypeWithAnnotations.Create(CreateErrorType("var"));
}
else
{
Debug.Assert(declType.HasType);
}
iterationVariableType = declType;
boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType);
SourceLocalSymbol local = this.IterationVariable;
local.SetTypeWithAnnotations(declType);
CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declType.Type, diagnostics, typeSyntax);
if (local.Scope == ScopedKind.ScopedValue && !declType.Type.IsErrorOrRefLikeOrAllowsRefLikeType())
{
diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location);
}
if (local.RefKind != RefKind.None)
{
if (CheckRefLocalInAsyncOrIteratorMethod(local.IdentifierToken, diagnostics))
{
hasErrors = true;
}
}
if (!hasErrors)
{
BindValueKind requiredCurrentKind;
switch (local.RefKind)
{
case RefKind.None:
requiredCurrentKind = BindValueKind.RValue;
break;
case RefKind.Ref:
requiredCurrentKind = BindValueKind.Assignable | BindValueKind.RefersToLocation;
break;
case RefKind.RefReadOnly:
requiredCurrentKind = BindValueKind.RefersToLocation;
break;
default:
throw ExceptionUtilities.UnexpectedValue(local.RefKind);
}
if (builder.InlineArraySpanType == WellKnownType.Unknown)
{
hasErrors |= !CheckMethodReturnValueKind(
builder.CurrentPropertyGetter,
callSyntaxOpt: null,
collectionExpr.Syntax,
requiredCurrentKind,
checkingReceiver: false,
diagnostics);
}
else
{
hasErrors |= !CheckValueKind(collectionExpr.Syntax, collectionExpr, requiredCurrentKind, checkingReceiver: false, diagnostics);
}
}
break;
}
case SyntaxKind.ForEachVariableStatement:
{
var node = (ForEachVariableStatementSyntax)_syntax;
iterationVariableType = inferredType.HasType ? inferredType : TypeWithAnnotations.Create(CreateErrorType("var"));
var variables = node.Variable;
if (variables.IsDeconstructionLeft())
{
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, isDiscardExpression: false, iterationVariableType.Type).MakeCompilerGenerated();
DeclarationExpressionSyntax declaration = null;
ExpressionSyntax expression = null;
BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction(
variables,
variables,
right: _syntax.Expression,
diagnostics: diagnostics,
rightPlaceholder: valuePlaceholder,
declaration: ref declaration,
expression: ref expression);
if (expression != null)
{
// error: must declare foreach loop iteration variables.
Error(diagnostics, ErrorCode.ERR_MustDeclareForeachIteration, variables);
hasErrors = true;
}
deconstructStep = new BoundForEachDeconstructStep(variables, deconstruction, valuePlaceholder).MakeCompilerGenerated();
}
else
{
// Bind the expression for error recovery, but discard all new diagnostics
iterationErrorExpression = BindToTypeForErrorRecovery(BindExpression(node.Variable, BindingDiagnosticBag.Discarded));
if (iterationErrorExpression.Kind == BoundKind.DiscardExpression)
{
iterationErrorExpression = ((BoundDiscardExpression)iterationErrorExpression).FailInference(this, diagnosticsOpt: null);
}
hasErrors = true;
if (!node.HasErrors)
{
Error(diagnostics, ErrorCode.ERR_MustDeclareForeachIteration, variables);
}
}
boundIterationVariableType = new BoundTypeExpression(variables, aliasOpt: null, typeWithAnnotations: iterationVariableType).MakeCompilerGenerated();
break;
}
default:
throw ExceptionUtilities.UnexpectedValue(_syntax.Kind());
}
BoundStatement body = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics);
// NOTE: in error cases, binder may collect all kind of variables, not just formally declared iteration variables.
// As a matter of error recovery, we will treat such variables the same as the iteration variables.
// I.E. - they will be considered declared and assigned in each iteration step.
ImmutableArray<LocalSymbol> iterationVariables = this.Locals;
Debug.Assert(hasErrors ||
_syntax.HasErrors ||
iterationVariables.All(local => local.DeclarationKind == LocalDeclarationKind.ForEachIterationVariable),
"Should not have iteration variables that are not ForEachIterationVariable in valid code");
hasErrors = hasErrors || boundIterationVariableType.HasErrors || iterationVariableType.Type.IsErrorType();
// Skip the conversion checks and array/enumerator differentiation if we know we have an error (except local name conflicts).
if (hasErrors)
{
return new BoundForEachStatement(
_syntax,
enumeratorInfoOpt: null, // can't be sure that it's complete
elementPlaceholder: null,
elementConversion: null,
boundIterationVariableType,
iterationVariables,
iterationErrorExpression,
collectionExpr,
deconstructStep,
awaitInfo,
body,
this.BreakLabel,
this.ContinueLabel,
hasErrors);
}
hasErrors |= hasNameConflicts;
var foreachKeyword = _syntax.ForEachKeyword;
ReportDiagnosticsIfObsolete(diagnostics, getEnumeratorMethod, foreachKeyword, hasBaseReceiver: false);
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, getEnumeratorMethod, foreachKeyword, isDelegateConversion: false);
// MoveNext is an instance method, so it does not need to have unmanaged callers only diagnostics reported.
// Either a diagnostic was reported at the declaration of the method (for the invalid attribute), or MoveNext
// is marked as not supported and we won't get here in the first place (for metadata import).
ReportDiagnosticsIfObsolete(diagnostics, builder.MoveNextInfo.Method, foreachKeyword, hasBaseReceiver: false);
ReportDiagnosticsIfObsolete(diagnostics, builder.CurrentPropertyGetter, foreachKeyword, hasBaseReceiver: false);
ReportDiagnosticsIfObsolete(diagnostics, builder.CurrentPropertyGetter.AssociatedSymbol, foreachKeyword, hasBaseReceiver: false);
// We want to convert from inferredType in the array/string case and builder.ElementType in the enumerator case,
// but it turns out that these are equivalent (when both are available).
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
Conversion elementConversionClassification = this.Conversions.ClassifyConversionFromType(inferredType.Type, iterationVariableType.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo, forCast: true);
if (elementConversionClassification.Kind != ConversionKind.Identity && IterationVariable.RefKind is RefKind.Ref or RefKind.RefReadOnly)
{
Error(diagnostics, ErrorCode.ERR_RefAssignmentMustHaveIdentityConversion, collectionExpr.Syntax, iterationVariableType.Type);
hasErrors = true;
}
var elementPlaceholder = new BoundValuePlaceholder(_syntax, inferredType.Type).MakeCompilerGenerated();
BindingDiagnosticBag createConversionDiagnostics;
if (!elementConversionClassification.IsValid)
{
ImmutableArray<MethodSymbol> originalUserDefinedConversions = elementConversionClassification.OriginalUserDefinedConversions;
if (originalUserDefinedConversions.Length > 1)
{
diagnostics.Add(ErrorCode.ERR_AmbigUDConv, foreachKeyword.GetLocation(), originalUserDefinedConversions[0], originalUserDefinedConversions[1], inferredType.Type, iterationVariableType);
}
else
{
SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType.Type, iterationVariableType.Type);
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, foreachKeyword.GetLocation(), distinguisher.First, distinguisher.Second);
}
hasErrors = true;
createConversionDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: false, withDependencies: false);
}
else
{
createConversionDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
}
BoundExpression elementConversion = CreateConversion(_syntax, elementPlaceholder, elementConversionClassification, isCast: false, conversionGroupOpt: null, iterationVariableType.Type, createConversionDiagnostics);
if (createConversionDiagnostics.AccumulatesDiagnostics && !createConversionDiagnostics.DiagnosticBag.IsEmptyWithoutResolution)
{
diagnostics.AddDependencies(createConversionDiagnostics);
var location = _syntax.ForEachKeyword.GetLocation();
foreach (var d in createConversionDiagnostics.DiagnosticBag.AsEnumerableWithoutResolution())
{
diagnostics.Add(d.WithLocation(location));
}
}
else
{
diagnostics.AddRange(createConversionDiagnostics);
}
createConversionDiagnostics.Free();
// Spec (§8.8.4):
// If the type X of expression is dynamic then there is an implicit conversion from >>expression<< (not the type of the expression)
// to the System.Collections.IEnumerable interface (§6.1.8).
Conversion collectionConversionClassification = this.Conversions.ClassifyConversionFromExpression(collectionExpr, builder.CollectionType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
Conversion currentConversionClassification = this.Conversions.ClassifyConversionFromType(builder.CurrentPropertyGetter.ReturnType, builder.ElementType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
TypeSymbol getEnumeratorType = getEnumeratorMethod.ReturnType;
if (builder.InlineArraySpanType == WellKnownType.Unknown && getEnumeratorType.IsRestrictedType() && (IsDirectlyInIterator || IsInAsyncMethod()))
{
CheckFeatureAvailability(foreachKeyword, MessageID.IDS_FeatureRefUnsafeInIteratorAsync, diagnostics);
}
diagnostics.Add(_syntax.ForEachKeyword, useSiteInfo);
// Due to the way we extracted the various types, these conversions should always be possible.
// CAVEAT: if we're iterating over an array of pointers, the current conversion will fail since we
// can't convert from object to a pointer type. Similarly, if we're iterating over an array of
// Nullable<Error>, the current conversion will fail because we don't know if an ErrorType is a
// value type. This doesn't matter in practice, since we won't actually use the enumerator pattern
// when we lower the loop.
Debug.Assert(collectionConversionClassification.IsValid);
Debug.Assert(currentConversionClassification.IsValid ||
(builder.ElementType.IsPointerOrFunctionPointer() && collectionExpr.Type.IsArray()) ||
(builder.ElementType.IsNullableType() && builder.ElementType.GetMemberTypeArgumentsNoUseSiteDiagnostics().Single().IsErrorType() && collectionExpr.Type.IsArray()));
// If user-defined conversions could occur here, we would need to check for ObsoleteAttribute.
Debug.Assert((object)collectionConversionClassification.Method == null,
"Conversion from collection expression to collection type should not be user-defined");
Debug.Assert((object)currentConversionClassification.Method == null,
"Conversion from Current property type to element type should not be user-defined");
BoundExpression convertedCollectionExpression = ConvertForEachCollection(collectionExpr, collectionConversionClassification, builder.CollectionType, diagnostics);
if (currentConversionClassification.IsValid)
{
builder.CurrentPlaceholder = new BoundValuePlaceholder(_syntax, builder.CurrentPropertyGetter.ReturnType).MakeCompilerGenerated();
builder.CurrentConversion = CreateConversion(_syntax, builder.CurrentPlaceholder, currentConversionClassification, isCast: false, conversionGroupOpt: null, builder.ElementType, diagnostics);
}
if (builder.NeedsDisposal && IsAsync)
{
hasErrors |= GetAwaitDisposeAsyncInfo(ref builder, diagnostics);
}
Debug.Assert(
hasErrors ||
collectionConversionClassification.IsIdentity ||
(collectionConversionClassification.IsImplicit &&
(IsIEnumerable(builder.CollectionType) ||
IsIEnumerableT(builder.CollectionType.OriginalDefinition, IsAsync, Compilation) ||
builder.GetEnumeratorInfo.Method.IsExtensionMethod)) ||
// For compat behavior, we can enumerate over System.String even if it's not IEnumerable. That will
// result in an explicit reference conversion in the bound nodes, but that conversion won't be emitted.
(collectionConversionClassification.Kind == ConversionKind.ExplicitReference && collectionExpr.Type.SpecialType == SpecialType.System_String));
return new BoundForEachStatement(
_syntax,
builder.Build(this.Flags),
elementPlaceholder,
elementConversion,
boundIterationVariableType,
iterationVariables,
iterationErrorExpression,
convertedCollectionExpression,
deconstructStep,
awaitInfo,
body,
this.BreakLabel,
this.ContinueLabel,
hasErrors);
}
private bool GetAwaitDisposeAsyncInfo(ref ForEachEnumeratorInfo.Builder builder, BindingDiagnosticBag diagnostics)
{
var awaitableType = builder.PatternDisposeInfo is null
? this.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask, diagnostics, this._syntax)
: builder.PatternDisposeInfo.Method.ReturnType;
bool hasErrors = false;
var expr = _syntax.Expression;
ReportBadAwaitDiagnostics(_syntax.AwaitKeyword, diagnostics, ref hasErrors);
var placeholder = new BoundAwaitableValuePlaceholder(expr, awaitableType);
builder.DisposeAwaitableInfo = BindAwaitInfo(placeholder, expr, diagnostics, ref hasErrors);
return hasErrors;
}
internal TypeWithAnnotations InferCollectionElementType(BindingDiagnosticBag diagnostics, ExpressionSyntax collectionSyntax)
{
// Use the right binder to avoid seeing iteration variable
BoundExpression collectionExpr = this.GetBinder(collectionSyntax).BindValue(collectionSyntax, diagnostics, BindValueKind.RValue);
GetEnumeratorInfoAndInferCollectionElementType(_syntax, collectionSyntax, ref collectionExpr, isAsync: IsAsync, isSpread: false, diagnostics, out TypeWithAnnotations inferredType, builder: out _);
return inferredType;
}
}
partial class Binder
{
protected BoundExpression ConvertForEachCollection(
BoundExpression collectionExpr,
Conversion collectionConversionClassification,
TypeSymbol collectionType,
BindingDiagnosticBag diagnostics)
{
// We're wrapping the collection expression in a (non-synthesized) conversion so that its converted
// type (i.e. builder.CollectionType) will be available in the binding API.
Debug.Assert(!collectionConversionClassification.IsUserDefined);
BoundExpression convertedCollectionExpression = CreateConversion(
collectionExpr.Syntax,
collectionExpr,
collectionConversionClassification,
isCast: false,
conversionGroupOpt: null,
collectionType,
diagnostics);
if ((convertedCollectionExpression as BoundConversion)?.Operand != (object)collectionExpr)
{
Debug.Assert(collectionConversionClassification.IsIdentity);
Debug.Assert(convertedCollectionExpression == (object)collectionExpr);
Debug.Assert(collectionType.Equals(collectionExpr.Type, TypeCompareKind.AllIgnoreOptions)); // Should not create an Identity conversion that changes type.
convertedCollectionExpression = new BoundConversion(
collectionExpr.Syntax,
collectionExpr,
collectionConversionClassification,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: false,
conversionGroupOpt: null,
ConstantValue.NotAvailable,
collectionType);
}
return convertedCollectionExpression;
}
internal bool GetEnumeratorInfoAndInferCollectionElementType(
SyntaxNode syntax,
SyntaxNode collectionSyntax,
ref BoundExpression collectionExpr,
bool isAsync,
bool isSpread,
BindingDiagnosticBag diagnostics,
out TypeWithAnnotations inferredType,
out ForEachEnumeratorInfo.Builder builder)
{
Debug.Assert(!isAsync || !isSpread);
bool gotInfo = GetEnumeratorInfo(syntax, collectionSyntax, ref collectionExpr, isAsync, isSpread, diagnostics, out builder);
if (!gotInfo)
{
inferredType = default;
}
else if (collectionExpr.HasDynamicType())
{
// If the enumerator is dynamic, it yields dynamic values
inferredType = TypeWithAnnotations.Create(DynamicTypeSymbol.Instance);
}
else if (collectionExpr.Type.SpecialType == SpecialType.System_String && builder.CollectionType.SpecialType == SpecialType.System_Collections_IEnumerable)
{
// Reproduce dev11 behavior: we're always going to lower a foreach loop over a string to a for loop
// over the string's Chars indexer. Therefore, we should infer "char", regardless of what the spec
// indicates the element type is. This actually matters in practice because the System.String in
// the portable library doesn't have a pattern GetEnumerator method or implement IEnumerable<char>.
inferredType = TypeWithAnnotations.Create(GetSpecialType(SpecialType.System_Char, diagnostics, collectionExpr.Syntax));
}
else
{
inferredType = builder.ElementTypeWithAnnotations;
}
return gotInfo;
}
private BoundExpression UnwrapCollectionExpressionIfNullable(BoundExpression collectionExpr, BindingDiagnosticBag diagnostics)
{
TypeSymbol collectionExprType = collectionExpr.Type;
// If collectionExprType is a nullable type, then use the underlying type and take the value (i.e. .Value) of collectionExpr.
// This behavior is not spec'd, but it's what Dev10 does.
if ((object)collectionExprType != null && collectionExprType.IsNullableType())
{
SyntaxNode exprSyntax = collectionExpr.Syntax;
MethodSymbol nullableValueGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_Value, diagnostics, exprSyntax);
if ((object)nullableValueGetter != null)
{
nullableValueGetter = nullableValueGetter.AsMember((NamedTypeSymbol)collectionExprType);
// Synthesized call, because we don't want to modify the type in the SemanticModel.
return BoundCall.Synthesized(
syntax: exprSyntax,
receiverOpt: collectionExpr,
initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(collectionExpr, nullableValueGetter),
method: nullableValueGetter);
}
else
{
return new BoundBadExpression(
exprSyntax,
LookupResultKind.Empty,
ImmutableArray<Symbol>.Empty,
ImmutableArray.Create(collectionExpr),
collectionExprType.GetNullableUnderlyingType())
{ WasCompilerGenerated = true }; // Don't affect the type in the SemanticModel.
}
}
return collectionExpr;
}
/// <summary>
/// The spec describes an algorithm for finding the following types:
/// 1) Collection type
/// 2) Enumerator type
/// 3) Element type
///
/// The implementation details are a bit different. If we're iterating over a string or an array, then we don't need to record anything
/// but the inferredType (in case the iteration variable is implicitly typed). If we're iterating over anything else, then we want the
/// inferred type plus a ForEachEnumeratorInfo.Builder with:
/// 1) Collection type
/// 2) Element type
/// 3) GetEnumerator (or GetAsyncEnumerator) method of the collection type (return type will be the enumerator type from the spec)
/// 4) Current property and MoveNext (or MoveNextAsync) method of the enumerator type
///
/// The caller will have to do some extra conversion checks before creating a ForEachEnumeratorInfo for the BoundForEachStatement.
/// </summary>
/// <param name="builder">Builder to fill in (partially, all but conversions).</param>
/// <param name="collectionExpr">The expression over which to iterate.</param>
/// <param name="diagnostics">Populated with binding diagnostics.</param>
/// <returns>Partially populated (all but conversions) or null if there was an error.</returns>
private bool GetEnumeratorInfo(
SyntaxNode syntax,
SyntaxNode collectionSyntax,
ref BoundExpression collectionExpr,
bool isAsync,
bool isSpread,
BindingDiagnosticBag diagnostics,
out ForEachEnumeratorInfo.Builder builder)
{
Debug.Assert(!isAsync || !isSpread);
BoundExpression originalCollectionExpr = collectionExpr;
EnumeratorResult found = GetEnumeratorInfoCore(syntax, collectionSyntax, ref collectionExpr, isAsync, diagnostics, out builder);
switch (found)
{
case EnumeratorResult.Succeeded:
return true;
case EnumeratorResult.FailedAndReported:
return false;
}
TypeSymbol collectionExprType = collectionExpr.Type;
if (string.IsNullOrEmpty(collectionExprType.Name) && collectionExpr.HasErrors)
{
return false;
}
if (collectionExprType.IsErrorType())
{
return false;
}
// Retry with a different assumption about whether the foreach is async
bool wrongAsync = GetEnumeratorInfoCore(syntax, collectionSyntax, ref originalCollectionExpr, !isAsync, BindingDiagnosticBag.Discarded, builder: out _) == EnumeratorResult.Succeeded;
ErrorCode errorCode = (wrongAsync, isAsync, isSpread) switch
{
(true, true, _) => ErrorCode.ERR_AwaitForEachMissingMemberWrongAsync,
(true, false, _) => ErrorCode.ERR_ForEachMissingMemberWrongAsync,
(false, true, _) => ErrorCode.ERR_AwaitForEachMissingMember,
(false, false, true) => ErrorCode.ERR_SpreadMissingMember,
(false, false, false) => ErrorCode.ERR_ForEachMissingMember,
};
diagnostics.Add(errorCode, collectionSyntax.Location,
collectionExprType, isAsync ? WellKnownMemberNames.GetAsyncEnumeratorMethodName : WellKnownMemberNames.GetEnumeratorMethodName);
return false;
}
private enum EnumeratorResult
{
Succeeded,
FailedNotReported,
FailedAndReported
}
private EnumeratorResult GetEnumeratorInfoCore(SyntaxNode syntax, SyntaxNode collectionSyntax, ref BoundExpression collectionExpr, bool isAsync, BindingDiagnosticBag diagnostics, out ForEachEnumeratorInfo.Builder builder)
{
EnumeratorResult result;
if (!isAsync && collectionExpr.Type?.HasInlineArrayAttribute(out _) == true && collectionExpr.Type.TryGetPossiblyUnsupportedByLanguageInlineArrayElementField() is FieldSymbol elementField)
{
WellKnownType wellKnownSpan;
bool usedAsValue = false;
if (CheckValueKind(collectionExpr.Syntax, collectionExpr, BindValueKind.RefersToLocation | BindValueKind.Assignable, checkingReceiver: false, BindingDiagnosticBag.Discarded))
{
wellKnownSpan = WellKnownType.System_Span_T;
}
else
{
wellKnownSpan = WellKnownType.System_ReadOnlySpan_T;
if (!CheckValueKind(collectionExpr.Syntax, collectionExpr, BindValueKind.RefersToLocation, checkingReceiver: false, BindingDiagnosticBag.Discarded))
{
usedAsValue = true;
}
}
NamedTypeSymbol spanType = GetWellKnownType(wellKnownSpan, diagnostics, collectionExpr.Syntax);
if (spanType.IsErrorType())
{
builder = new ForEachEnumeratorInfo.Builder();
return EnumeratorResult.FailedAndReported;
}
spanType = spanType.Construct(ImmutableArray.Create(elementField.TypeWithAnnotations));
if (!TypeSymbol.IsInlineArrayElementFieldSupported(elementField))
{
diagnostics.Add(ErrorCode.ERR_InlineArrayForEachNotSupported, collectionExpr.Syntax.GetLocation(), collectionExpr.Type);
builder = new ForEachEnumeratorInfo.Builder();
return EnumeratorResult.FailedAndReported;
}
var enumeratorInfoDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
BoundExpression span = new BoundValuePlaceholder(collectionExpr.Syntax, spanType).MakeCompilerGenerated();
#if DEBUG
var originalSpan = span;
#endif
result = getEnumeratorInfo(syntax, collectionSyntax, ref span, isAsync: false, enumeratorInfoDiagnostics, out builder);
#if DEBUG
Debug.Assert(span == originalSpan);
Debug.Assert(!builder.ViaExtensionMethod || builder.GetEnumeratorInfo.Method.IsExtensionMethod);
#endif
if (!builder.ViaExtensionMethod &&
((result is EnumeratorResult.Succeeded && builder.ElementTypeWithAnnotations.Equals(elementField.TypeWithAnnotations, TypeCompareKind.AllIgnoreOptions) &&
builder.CurrentPropertyGetter?.RefKind == (wellKnownSpan == WellKnownType.System_ReadOnlySpan_T ? RefKind.RefReadOnly : RefKind.Ref)) ||
result is EnumeratorResult.FailedAndReported))
{
Debug.Assert(builder.CollectionType == (object)spanType);
builder.CollectionType = collectionExpr.Type;
builder.InlineArraySpanType = wellKnownSpan;
builder.InlineArrayUsedAsValue = usedAsValue;
diagnostics.AddRangeAndFree(enumeratorInfoDiagnostics);
CheckFeatureAvailability(collectionExpr.Syntax, MessageID.IDS_FeatureInlineArrays, diagnostics);
if (result == EnumeratorResult.Succeeded)
{
if (wellKnownSpan == WellKnownType.System_ReadOnlySpan_T)
{
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_Unsafe__AsRef_T, diagnostics, syntax: collectionExpr.Syntax);
}
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_Unsafe__Add_T, diagnostics, syntax: collectionExpr.Syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_Unsafe__As_T, diagnostics, syntax: collectionExpr.Syntax);
CheckInlineArrayTypeIsSupported(collectionExpr.Syntax, collectionExpr.Type, elementField.Type, diagnostics);
}
return result;
}
enumeratorInfoDiagnostics.Free();
diagnostics.Add(ErrorCode.ERR_InlineArrayForEachNotSupported, collectionExpr.Syntax.GetLocation(), collectionExpr.Type);
builder = new ForEachEnumeratorInfo.Builder();
return EnumeratorResult.FailedAndReported;
}
#if DEBUG
var originalCollectionExpr = collectionExpr;
#endif
result = getEnumeratorInfo(syntax, collectionSyntax, ref collectionExpr, isAsync, diagnostics, out builder);
#if DEBUG
Debug.Assert(collectionExpr == originalCollectionExpr ||
(originalCollectionExpr.Type?.IsNullableType() == true && originalCollectionExpr.Type.StrippedType().Equals(collectionExpr.Type, TypeCompareKind.AllIgnoreOptions)));
Debug.Assert(!builder.ViaExtensionMethod || builder.GetEnumeratorInfo.Method.IsExtensionMethod);
#endif
return result;
EnumeratorResult getEnumeratorInfo(SyntaxNode syntax, SyntaxNode collectionSyntax, ref BoundExpression collectionExpr, bool isAsync, BindingDiagnosticBag diagnostics, out ForEachEnumeratorInfo.Builder builder)
{
builder = new ForEachEnumeratorInfo.Builder();
builder.IsAsync = isAsync;
TypeSymbol collectionExprType = collectionExpr.Type;
if (collectionExprType is null) // There's no way to enumerate something without a type.
{
if (!ReportConstantNullCollectionExpr(collectionExpr, diagnostics))
{
// Anything else with a null type is a method group or anonymous function
diagnostics.Add(ErrorCode.ERR_AnonMethGrpInForEach, collectionSyntax.Location, collectionExpr.Display);
}
// CONSIDER: dev10 also reports ERR_ForEachMissingMember (i.e. failed pattern match).
return EnumeratorResult.FailedAndReported;
}
if (collectionExpr.ResultKind == LookupResultKind.NotAValue)
{
// Short-circuiting to prevent strange behavior in the case where the collection
// expression is a type expression and the type is enumerable.
Debug.Assert(collectionExpr.HasAnyErrors); // should already have been reported
return EnumeratorResult.FailedAndReported;
}
if (collectionExprType.Kind == SymbolKind.DynamicType && isAsync)
{
diagnostics.Add(ErrorCode.ERR_BadDynamicAwaitForEach, collectionSyntax.Location);
return EnumeratorResult.FailedAndReported;
}
// The spec specifically lists the collection, enumerator, and element types for arrays and dynamic.
if (collectionExprType.Kind == SymbolKind.ArrayType || collectionExprType.Kind == SymbolKind.DynamicType)
{
if (ReportConstantNullCollectionExpr(collectionExpr, diagnostics))
{
return EnumeratorResult.FailedAndReported;
}
builder = GetDefaultEnumeratorInfo(syntax, builder, diagnostics, collectionExprType);
return EnumeratorResult.Succeeded;
}
var unwrappedCollectionExpr = UnwrapCollectionExpressionIfNullable(collectionExpr, diagnostics);
var unwrappedCollectionExprType = unwrappedCollectionExpr.Type;
if (SatisfiesGetEnumeratorPattern(syntax, collectionSyntax, ref builder, unwrappedCollectionExpr, isAsync, viaExtensionMethod: false, diagnostics))
{
collectionExpr = unwrappedCollectionExpr;
if (ReportConstantNullCollectionExpr(collectionExpr, diagnostics))
{
return EnumeratorResult.FailedAndReported;
}
return createPatternBasedEnumeratorResult(ref builder, unwrappedCollectionExpr, isAsync, viaExtensionMethod: false, diagnostics);
}
if (!isAsync && IsIEnumerable(unwrappedCollectionExprType))
{
collectionExpr = unwrappedCollectionExpr;
// This indicates a problem with the special IEnumerable type - it should have satisfied the GetEnumerator pattern.
diagnostics.Add(ErrorCode.ERR_ForEachMissingMember, collectionSyntax.Location, unwrappedCollectionExprType, WellKnownMemberNames.GetEnumeratorMethodName);
return EnumeratorResult.FailedAndReported;
}
if (isAsync && IsIAsyncEnumerable(unwrappedCollectionExprType))
{
collectionExpr = unwrappedCollectionExpr;
// This indicates a problem with the well-known IAsyncEnumerable type - it should have satisfied the GetAsyncEnumerator pattern.
diagnostics.Add(ErrorCode.ERR_AwaitForEachMissingMember, collectionSyntax.Location, unwrappedCollectionExprType, WellKnownMemberNames.GetAsyncEnumeratorMethodName);
return EnumeratorResult.FailedAndReported;
}
if (SatisfiesIEnumerableInterfaces(collectionSyntax, ref builder, unwrappedCollectionExpr, isAsync, diagnostics, unwrappedCollectionExprType) is not EnumeratorResult.FailedNotReported and var result)
{
collectionExpr = unwrappedCollectionExpr;
return result;
}
// COMPAT:
// In some rare cases, like MicroFramework, System.String does not implement foreach pattern.
// For compat reasons we must still treat System.String as valid to use in a foreach
// Similarly to the cases with array and dynamic, we will default to IEnumerable for binding purposes.
// Lowering will not use iterator info with strings, so it is ok.
if (!isAsync && collectionExprType.SpecialType == SpecialType.System_String)
{
if (ReportConstantNullCollectionExpr(collectionExpr, diagnostics))
{
return EnumeratorResult.FailedAndReported;
}
builder = GetDefaultEnumeratorInfo(syntax, builder, diagnostics, collectionExprType);
return EnumeratorResult.Succeeded;
}
if (SatisfiesGetEnumeratorPattern(syntax, collectionSyntax, ref builder, collectionExpr, isAsync, viaExtensionMethod: true, diagnostics))
{
return createPatternBasedEnumeratorResult(ref builder, collectionExpr, isAsync, viaExtensionMethod: true, diagnostics);
}
return EnumeratorResult.FailedNotReported;
}
EnumeratorResult createPatternBasedEnumeratorResult(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, bool viaExtensionMethod, BindingDiagnosticBag diagnostics)
{
Debug.Assert((object)builder.GetEnumeratorInfo != null);
Debug.Assert(!(viaExtensionMethod && builder.GetEnumeratorInfo.Method.Parameters.IsDefaultOrEmpty));
builder.ViaExtensionMethod = viaExtensionMethod;
builder.CollectionType = viaExtensionMethod
? builder.GetEnumeratorInfo.Method.Parameters[0].Type
: collectionExpr.Type;
if (SatisfiesForEachPattern(syntax, collectionSyntax, ref builder, isAsync, diagnostics))
{
builder.ElementTypeWithAnnotations = ((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol).TypeWithAnnotations;
GetDisposalInfoForEnumerator(syntax, ref builder, collectionExpr, isAsync, diagnostics);
return EnumeratorResult.Succeeded;
}
MethodSymbol getEnumeratorMethod = builder.GetEnumeratorInfo.Method;
diagnostics.Add(isAsync ? ErrorCode.ERR_BadGetAsyncEnumerator : ErrorCode.ERR_BadGetEnumerator, collectionSyntax.Location, getEnumeratorMethod.ReturnType, getEnumeratorMethod);
return EnumeratorResult.FailedAndReported;
}
}
private EnumeratorResult SatisfiesIEnumerableInterfaces(SyntaxNode collectionSyntax, ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, BindingDiagnosticBag diagnostics, TypeSymbol unwrappedCollectionExprType)
{
if (!AllInterfacesContainsIEnumerable(collectionSyntax, ref builder, unwrappedCollectionExprType, isAsync, diagnostics, out bool foundMultipleGenericIEnumerableInterfaces))
{
return EnumeratorResult.FailedNotReported;
}
if (ReportConstantNullCollectionExpr(collectionExpr, diagnostics))
{
return EnumeratorResult.FailedAndReported;
}
SyntaxNode errorLocationSyntax = collectionSyntax;
if (foundMultipleGenericIEnumerableInterfaces)
{
diagnostics.Add(isAsync ? ErrorCode.ERR_MultipleIAsyncEnumOfT : ErrorCode.ERR_MultipleIEnumOfT, errorLocationSyntax.Location, unwrappedCollectionExprType,
isAsync ?
this.Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T) :
this.Compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T));
return EnumeratorResult.FailedAndReported;
}
Debug.Assert((object)builder.CollectionType != null);
NamedTypeSymbol collectionType = (NamedTypeSymbol)builder.CollectionType;
if (unwrappedCollectionExprType.IsRefLikeOrAllowsRefLikeType())
{
builder.CollectionType = unwrappedCollectionExprType;
}
if (collectionType.IsGenericType)
{
// If the type is generic, we have to search for the methods
builder.ElementTypeWithAnnotations = collectionType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single();
MethodSymbol getEnumeratorMethod;
if (isAsync)
{
Debug.Assert(IsIAsyncEnumerable(collectionType.OriginalDefinition));
getEnumeratorMethod = (MethodSymbol)GetWellKnownTypeMember(Compilation, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator,
diagnostics, errorLocationSyntax.Location, isOptional: false);
// Well-known members are matched by signature: we shouldn't find it if it doesn't have exactly 1 parameter.
Debug.Assert(getEnumeratorMethod is null or { ParameterCount: 1 });
if (getEnumeratorMethod?.Parameters[0].IsOptional == false)
{
// This indicates a problem with the well-known IAsyncEnumerable type - it should have an optional cancellation token.
diagnostics.Add(ErrorCode.ERR_AwaitForEachMissingMember, collectionSyntax.Location, unwrappedCollectionExprType, WellKnownMemberNames.GetAsyncEnumeratorMethodName);
return EnumeratorResult.FailedAndReported;
}
}
else
{
Debug.Assert(collectionType.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T);
getEnumeratorMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_Generic_IEnumerable_T__GetEnumerator, diagnostics, errorLocationSyntax);
}
MethodSymbol moveNextMethod = null;
if ((object)getEnumeratorMethod != null)
{
MethodSymbol specificGetEnumeratorMethod = getEnumeratorMethod.AsMember(collectionType);
TypeSymbol enumeratorType = specificGetEnumeratorMethod.ReturnType;
// IAsyncEnumerable<T>.GetAsyncEnumerator has a default param, so let's fill it in
builder.GetEnumeratorInfo = BindDefaultArguments(
specificGetEnumeratorMethod,
extensionReceiverOpt: null,
expanded: false,
collectionExpr.Syntax,
diagnostics);
MethodSymbol currentPropertyGetter;
if (isAsync)
{
Debug.Assert(enumeratorType.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T)));
MethodSymbol moveNextAsync = (MethodSymbol)GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__MoveNextAsync,
diagnostics, errorLocationSyntax.Location, isOptional: false);
if ((object)moveNextAsync != null)
{
moveNextMethod = moveNextAsync.AsMember((NamedTypeSymbol)enumeratorType);
}
currentPropertyGetter = (MethodSymbol)GetWellKnownTypeMember(Compilation, WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__get_Current, diagnostics, errorLocationSyntax.Location, isOptional: false);
}
else
{
currentPropertyGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_Generic_IEnumerator_T__get_Current, diagnostics, errorLocationSyntax);
}
if ((object)currentPropertyGetter != null)
{
builder.CurrentPropertyGetter = currentPropertyGetter.AsMember((NamedTypeSymbol)enumeratorType);
}
}
if (!isAsync)
{
// NOTE: MoveNext is actually inherited from System.Collections.IEnumerator
moveNextMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__MoveNext, diagnostics, errorLocationSyntax);
}
// We're operating with well-known members: we know MoveNext/MoveNextAsync have no parameters
if (moveNextMethod is not null)
{
builder.MoveNextInfo = MethodArgumentInfo.CreateParameterlessMethod(moveNextMethod);
}
}
else
{
// Non-generic - use special members to avoid re-computing
Debug.Assert(collectionType.SpecialType == SpecialType.System_Collections_IEnumerable);
builder.GetEnumeratorInfo = GetParameterlessSpecialTypeMemberInfo(SpecialMember.System_Collections_IEnumerable__GetEnumerator, errorLocationSyntax, diagnostics);
builder.CurrentPropertyGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__get_Current, diagnostics, errorLocationSyntax);
builder.MoveNextInfo = GetParameterlessSpecialTypeMemberInfo(SpecialMember.System_Collections_IEnumerator__MoveNext, errorLocationSyntax, diagnostics);
builder.ElementTypeWithAnnotations = builder.CurrentPropertyGetter?.ReturnTypeWithAnnotations ?? TypeWithAnnotations.Create(GetSpecialType(SpecialType.System_Object, diagnostics, errorLocationSyntax));
Debug.Assert((object)builder.GetEnumeratorInfo == null ||
builder.GetEnumeratorInfo.Method.ReturnType.SpecialType == SpecialType.System_Collections_IEnumerator);
}
// We don't know the runtime type, so we will have to insert a runtime check for IDisposable (with a conditional call to IDisposable.Dispose).
builder.NeedsDisposal = true;
return EnumeratorResult.Succeeded;
}
private bool ReportConstantNullCollectionExpr(BoundExpression collectionExpr, BindingDiagnosticBag diagnostics)
{
if (collectionExpr.ConstantValueOpt is { IsNull: true })
{
// Spec seems to refer to null literals, but Dev10 reports anything known to be null.
diagnostics.Add(ErrorCode.ERR_NullNotValid, collectionExpr.Syntax.Location);
return true;
}
return false;
}
private void GetDisposalInfoForEnumerator(SyntaxNode syntax, ref ForEachEnumeratorInfo.Builder builder, BoundExpression expr, bool isAsync, BindingDiagnosticBag diagnostics)
{
// NOTE: if IDisposable is not available at all, no diagnostics will be reported - we will just assume that
// the enumerator is not disposable. If it has IDisposable in its interface list, there will be a diagnostic there.
// If IDisposable is available but its Dispose method is not, then diagnostics will be reported only if the enumerator
// is potentially disposable.
TypeSymbol enumeratorType = builder.GetEnumeratorInfo.Method.ReturnType;
if (enumeratorType.IsRefLikeType || isAsync)
{
var receiver = new BoundDisposableValuePlaceholder(syntax, enumeratorType);
BindingDiagnosticBag patternDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
MethodSymbol patternDisposeMethod = TryFindDisposePatternMethod(receiver, syntax, isAsync, patternDiagnostics, out bool expanded);
if (patternDisposeMethod is object)
{
Debug.Assert(!patternDisposeMethod.IsExtensionMethod);
Debug.Assert(patternDisposeMethod.ParameterRefKinds.IsDefaultOrEmpty ||
patternDisposeMethod.ParameterRefKinds.All(static refKind => refKind is RefKind.None or RefKind.In or RefKind.RefReadOnlyParameter));
diagnostics.AddRangeAndFree(patternDiagnostics);
var argsBuilder = ArrayBuilder<BoundExpression>.GetInstance(patternDisposeMethod.ParameterCount);
var argsToParams = default(ImmutableArray<int>);
BindDefaultArguments(
syntax,
patternDisposeMethod.Parameters,
argsBuilder,
argumentRefKindsBuilder: null,
namesBuilder: null,
ref argsToParams,
out BitVector defaultArguments,
expanded,
enableCallerInfo: true,
diagnostics);
builder.NeedsDisposal = true;
Debug.Assert(argsToParams.IsDefault);
builder.PatternDisposeInfo = new MethodArgumentInfo(patternDisposeMethod, argsBuilder.ToImmutableAndFree(), defaultArguments, expanded);
if (!isAsync)
{
// We already checked feature availability for async scenarios
CheckFeatureAvailability(expr.Syntax, MessageID.IDS_FeatureDisposalPattern, diagnostics);
}
return;
}
}
if (implementsInterface(enumeratorType, isAsync, diagnostics))
{
builder.NeedsDisposal = true;
return;
}
if (!enumeratorType.IsSealed && !isAsync) // For async foreach, we don't do the runtime check in unsealed case
{
Debug.Assert(!enumeratorType.IsRefLikeType); // Ref like types are supposed to be structs, therefore, sealed.
if (enumeratorType is TypeParameterSymbol { AllowsRefLikeType: true })
{
Error(diagnostics, ErrorCode.ERR_BadAllowByRefLikeEnumerator, expr.Syntax, enumeratorType);
}
else
{
builder.NeedsDisposal = true;
}
}
bool implementsInterface(TypeSymbol enumeratorType, bool isAsync, BindingDiagnosticBag diagnostics)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
NamedTypeSymbol targetInterface = isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable);
bool result = this.Conversions.HasImplicitConversionToOrImplementsVarianceCompatibleInterface(enumeratorType,
targetInterface,
ref useSiteInfo,
out bool needSupportForRefStructInterfaces);
diagnostics.Add(syntax, useSiteInfo);
if (needSupportForRefStructInterfaces &&
enumeratorType.ContainingModule != Compilation.SourceModule)
{
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureRefStructInterfaces, diagnostics);
}
return result;
}
}
private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(SyntaxNode syntax, ForEachEnumeratorInfo.Builder builder, BindingDiagnosticBag diagnostics, TypeSymbol collectionExprType)
{
// NOTE: for arrays, we won't actually use any of these members - they're just for the API.
builder.CollectionType = GetSpecialType(SpecialType.System_Collections_IEnumerable, diagnostics, syntax);
if (collectionExprType.IsDynamic())
{
builder.ElementTypeWithAnnotations = TypeWithAnnotations.Create(
((syntax as ForEachStatementSyntax)?.Type.IsVar == true) ?
(TypeSymbol)DynamicTypeSymbol.Instance :
GetSpecialType(SpecialType.System_Object, diagnostics, syntax));
}
else
{
builder.ElementTypeWithAnnotations = collectionExprType.SpecialType == SpecialType.System_String ?
TypeWithAnnotations.Create(GetSpecialType(SpecialType.System_Char, diagnostics, syntax)) :
((ArrayTypeSymbol)collectionExprType).ElementTypeWithAnnotations;
}
// CONSIDER:
// For arrays and string none of these members will actually be emitted, so it seems strange to prevent compilation if they can't be found.
// skip this work in the batch case?
builder.GetEnumeratorInfo = GetParameterlessSpecialTypeMemberInfo(SpecialMember.System_Collections_IEnumerable__GetEnumerator, syntax, diagnostics);
builder.CurrentPropertyGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__get_Current, diagnostics, syntax);
builder.MoveNextInfo = GetParameterlessSpecialTypeMemberInfo(SpecialMember.System_Collections_IEnumerator__MoveNext, syntax, diagnostics);
Debug.Assert((object)builder.GetEnumeratorInfo == null ||
TypeSymbol.Equals(builder.GetEnumeratorInfo.Method.ReturnType, this.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerator), TypeCompareKind.ConsiderEverything2));
// We don't know the runtime type, so we will have to insert a runtime check for IDisposable (with a conditional call to IDisposable.Dispose).
builder.NeedsDisposal = true;
return builder;
}
/// <summary>
/// Check for a GetEnumerator (or GetAsyncEnumerator) method on collectionExprType. Failing to satisfy the pattern is not an error -
/// it just means that we have to check for an interface instead.
/// </summary>
/// <param name="collectionExpr">Expression over which to iterate.</param>
/// <param name="diagnostics">Populated with *warnings* if there are near misses.</param>
/// <param name="builder">Builder to fill in. <see cref="ForEachEnumeratorInfo.Builder.GetEnumeratorInfo"/> set if the pattern in satisfied.</param>
/// <returns>True if the method was found (still have to verify that the return (i.e. enumerator) type is acceptable).</returns>
/// <remarks>
/// Only adds warnings, so does not affect control flow (i.e. no need to check for failure).
/// </remarks>
private bool SatisfiesGetEnumeratorPattern(SyntaxNode syntax, SyntaxNode collectionSyntax, ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, bool viaExtensionMethod, BindingDiagnosticBag diagnostics)
{
string methodName = isAsync ? WellKnownMemberNames.GetAsyncEnumeratorMethodName : WellKnownMemberNames.GetEnumeratorMethodName;
MethodArgumentInfo getEnumeratorInfo;
if (viaExtensionMethod)
{
getEnumeratorInfo = FindForEachPatternMethodViaExtension(syntax, collectionSyntax, collectionExpr, methodName, diagnostics);
}
else
{
var lookupResult = LookupResult.GetInstance();
getEnumeratorInfo = FindForEachPatternMethod(syntax, collectionSyntax, collectionExpr.Type, methodName, lookupResult, warningsOnly: true, diagnostics, isAsync);
lookupResult.Free();
}
builder.GetEnumeratorInfo = getEnumeratorInfo;
return (object)getEnumeratorInfo != null;
}
/// <summary>
/// Perform a lookup for the specified method on the specified type. Perform overload resolution
/// on the lookup results.
/// </summary>
/// <param name="patternType">Type to search.</param>
/// <param name="methodName">Method to search for.</param>
/// <param name="lookupResult">Passed in for reusability.</param>
/// <param name="warningsOnly">True if failures should result in warnings; false if they should result in errors.</param>
/// <param name="diagnostics">Populated with binding diagnostics.</param>
/// <returns>The desired method or null.</returns>
private MethodArgumentInfo FindForEachPatternMethod(SyntaxNode syntax, SyntaxNode collectionSyntax, TypeSymbol patternType, string methodName, LookupResult lookupResult, bool warningsOnly, BindingDiagnosticBag diagnostics, bool isAsync)
{
Debug.Assert(lookupResult.IsClear);
// Not using LookupOptions.MustBeInvocableMember because we don't want the corresponding lookup error.
// We filter out non-methods below.
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupMembersInType(
lookupResult,
patternType,
methodName,
arity: 0,
basesBeingResolved: null,
options: LookupOptions.Default,
originalBinder: this,
diagnose: false,
useSiteInfo: ref useSiteInfo);
diagnostics.Add(collectionSyntax, useSiteInfo);
if (!lookupResult.IsMultiViable)
{
ReportPatternMemberLookupDiagnostics(collectionSyntax, lookupResult, patternType, methodName, warningsOnly, diagnostics);
return null;
}
ArrayBuilder<MethodSymbol> candidateMethods = ArrayBuilder<MethodSymbol>.GetInstance();
foreach (Symbol member in lookupResult.Symbols)
{
if (member.Kind != SymbolKind.Method)
{
candidateMethods.Free();
if (warningsOnly)
{
ReportEnumerableWarning(collectionSyntax, diagnostics, patternType, member);
}
return null;
}
MethodSymbol method = (MethodSymbol)member;
// SPEC VIOLATION: The spec says we should apply overload resolution, but Dev10 uses
// some custom logic in ExpressionBinder.BindGrpToParams. The biggest difference
// we've found (so far) is that it only considers methods with expected number of parameters
// (i.e. doesn't work with "params" or optional parameters).
// Note: for pattern-based lookup for `await foreach` we accept `GetAsyncEnumerator` and
// `MoveNextAsync` methods with optional/params parameters.
if (method.ParameterCount == 0 || isAsync)
{
candidateMethods.Add((MethodSymbol)member);
}
}
MethodArgumentInfo patternInfo = PerformForEachPatternOverloadResolution(syntax, collectionSyntax, patternType, candidateMethods, warningsOnly, diagnostics, isAsync);
candidateMethods.Free();
return patternInfo;
}
/// <summary>
/// The overload resolution portion of FindForEachPatternMethod.
/// If no arguments are passed in, then an empty argument list will be used.
/// </summary>
private MethodArgumentInfo PerformForEachPatternOverloadResolution(SyntaxNode syntax, SyntaxNode collectionSyntax, TypeSymbol patternType, ArrayBuilder<MethodSymbol> candidateMethods, bool warningsOnly, BindingDiagnosticBag diagnostics, bool isAsync)
{
var analyzedArguments = AnalyzedArguments.GetInstance();
var typeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
var overloadResolutionResult = OverloadResolutionResult<MethodSymbol>.GetInstance();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
// We create a dummy receiver of the invocation so MethodInvocationOverloadResolution knows it was invoked from an instance, not a type
var dummyReceiver = new BoundImplicitReceiver(collectionSyntax, patternType);
this.OverloadResolution.MethodInvocationOverloadResolution(
methods: candidateMethods,
typeArguments: typeArguments,
receiver: dummyReceiver,
arguments: analyzedArguments,
result: overloadResolutionResult,
useSiteInfo: ref useSiteInfo,
options: OverloadResolution.Options.None);
diagnostics.Add(collectionSyntax, useSiteInfo);
MethodSymbol result = null;
MethodArgumentInfo info = null;
if (overloadResolutionResult.Succeeded)
{
result = overloadResolutionResult.ValidResult.Member;
if (result.IsStatic || result.DeclaredAccessibility != Accessibility.Public)
{
if (warningsOnly)
{
MessageID patternName = isAsync ? MessageID.IDS_FeatureAsyncStreams : MessageID.IDS_Collection;
diagnostics.Add(ErrorCode.WRN_PatternNotPublicOrNotInstance, collectionSyntax.Location, patternType, patternName.Localize(), result);
}
result = null;
}
else if (result.CallsAreOmitted(syntax.SyntaxTree))
{
// Calls to this method are omitted in the current syntax tree, i.e it is either a partial method with no implementation part OR a conditional method whose condition is not true in this source file.
// We don't want to allow this case.
result = null;
}
else
{
Debug.Assert(analyzedArguments.Arguments.Count == 0);
var argsToParams = overloadResolutionResult.ValidResult.Result.ArgsToParamsOpt;
var expanded = overloadResolutionResult.ValidResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm;
BindDefaultArguments(
syntax,
result.Parameters,
analyzedArguments.Arguments,
analyzedArguments.RefKinds,
analyzedArguments.Names,
ref argsToParams,
out BitVector defaultArguments,
expanded,
enableCallerInfo: true,
diagnostics);
Debug.Assert(argsToParams.IsDefault);
info = new MethodArgumentInfo(result, analyzedArguments.Arguments.ToImmutable(), defaultArguments, expanded);
}
}
else if (overloadResolutionResult.GetAllApplicableMembers() is var applicableMembers && applicableMembers.Length > 1)
{
if (warningsOnly)
{
diagnostics.Add(ErrorCode.WRN_PatternIsAmbiguous, collectionSyntax.Location, patternType, MessageID.IDS_Collection.Localize(),
applicableMembers[0], applicableMembers[1]);
}
}
overloadResolutionResult.Free();
analyzedArguments.Free();
typeArguments.Free();
return info;
}
private MethodArgumentInfo FindForEachPatternMethodViaExtension(SyntaxNode syntax, SyntaxNode collectionSyntax, BoundExpression collectionExpr, string methodName, BindingDiagnosticBag diagnostics)
{
var analyzedArguments = AnalyzedArguments.GetInstance();
var methodGroupResolutionResult = this.BindExtensionMethod(
collectionSyntax,
methodName,
analyzedArguments,
collectionExpr,
typeArgumentsWithAnnotations: default,
options: OverloadResolution.Options.None,
returnRefKind: default,
returnType: null,
withDependencies: diagnostics.AccumulatesDependencies);
diagnostics.AddRange(methodGroupResolutionResult.Diagnostics);
var overloadResolutionResult = methodGroupResolutionResult.OverloadResolutionResult;
if (overloadResolutionResult?.Succeeded ?? false)
{
var result = overloadResolutionResult.ValidResult.Member;
if (result.CallsAreOmitted(syntax.SyntaxTree))
{
// Calls to this method are omitted in the current syntax tree, i.e it is either a partial method with no implementation part OR a conditional method whose condition is not true in this source file.
// We don't want to allow this case.
methodGroupResolutionResult.Free();
analyzedArguments.Free();
return null;
}
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var collectionConversion = this.Conversions.ClassifyConversionFromExpression(collectionExpr, result.Parameters[0].Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
diagnostics.Add(syntax, useSiteInfo);
// Unconditionally convert here, to match what we set the ConvertedExpression to in the main BoundForEachStatement node.
Debug.Assert(!collectionConversion.IsUserDefined);
collectionExpr = new BoundConversion(
collectionExpr.Syntax,
collectionExpr,
collectionConversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: false,
conversionGroupOpt: null,
ConstantValue.NotAvailable,
result.Parameters[0].Type);
var info = BindDefaultArguments(
result,
collectionExpr,
expanded: overloadResolutionResult.ValidResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm,
collectionExpr.Syntax,
diagnostics);
methodGroupResolutionResult.Free();
analyzedArguments.Free();
return info;
}
else if (overloadResolutionResult?.GetAllApplicableMembers() is { } applicableMembers && applicableMembers.Length > 1)
{
diagnostics.Add(ErrorCode.WRN_PatternIsAmbiguous, collectionSyntax.Location, collectionExpr.Type, MessageID.IDS_Collection.Localize(),
applicableMembers[0], applicableMembers[1]);
}
else if (overloadResolutionResult != null)
{
overloadResolutionResult.ReportDiagnostics(
binder: this,
location: collectionSyntax.Location,
nodeOpt: collectionSyntax,
diagnostics: diagnostics,
name: methodName,
receiver: null,
invokedExpression: collectionSyntax,
arguments: methodGroupResolutionResult.AnalyzedArguments,
memberGroup: methodGroupResolutionResult.MethodGroup.Methods.ToImmutable(),
typeContainingConstructor: null,
delegateTypeBeingInvoked: null);
}
methodGroupResolutionResult.Free();
analyzedArguments.Free();
return null;
}
/// <summary>
/// Called after it is determined that the expression being enumerated is of a type that
/// has a GetEnumerator (or GetAsyncEnumerator) method. Checks to see if the return type of the GetEnumerator
/// method is suitable (i.e. has Current and MoveNext for regular case,
/// or Current and MoveNextAsync for async case).
/// </summary>
/// <param name="builder">Must be non-null and contain a non-null GetEnumeratorMethod.</param>
/// <param name="diagnostics">Will be populated with pattern diagnostics.</param>
/// <returns>True if the return type has suitable members.</returns>
/// <remarks>
/// It seems that every failure path reports the same diagnostics, so that is left to the caller.
/// </remarks>
private bool SatisfiesForEachPattern(SyntaxNode syntax, SyntaxNode collectionSyntax, ref ForEachEnumeratorInfo.Builder builder, bool isAsync, BindingDiagnosticBag diagnostics)
{
Debug.Assert((object)builder.GetEnumeratorInfo.Method != null);
MethodSymbol getEnumeratorMethod = builder.GetEnumeratorInfo.Method;
TypeSymbol enumeratorType = getEnumeratorMethod.ReturnType;
switch (enumeratorType.TypeKind)
{
case TypeKind.Class:
case TypeKind.Struct:
case TypeKind.Interface:
case TypeKind.TypeParameter: // Not specifically mentioned in the spec, but consistent with Dev10.
case TypeKind.Dynamic: // Not specifically mentioned in the spec, but consistent with Dev10.
break;
case TypeKind.Submission:
// submission class is synthesized and should never appear in a foreach:
throw ExceptionUtilities.UnexpectedValue(enumeratorType.TypeKind);
default:
return false;
}
// Use a try-finally since there are many return points
LookupResult lookupResult = LookupResult.GetInstance();
try
{
// If we searched for the accessor directly, we could reuse FindForEachPatternMethod and we
// wouldn't have to mangle CurrentPropertyName. However, Dev10 searches for the property and
// then extracts the accessor, so we should do the same (in case of accessors with non-standard
// names).
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupMembersInType(
lookupResult,
enumeratorType,
WellKnownMemberNames.CurrentPropertyName,
arity: 0,
basesBeingResolved: null,
options: LookupOptions.Default, // properties are not invocable - their accessors are
originalBinder: this,
diagnose: false,
useSiteInfo: ref useSiteInfo);
diagnostics.Add(collectionSyntax, useSiteInfo);
useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(useSiteInfo);
if (!lookupResult.IsSingleViable)
{
ReportPatternMemberLookupDiagnostics(collectionSyntax, lookupResult, enumeratorType, WellKnownMemberNames.CurrentPropertyName, warningsOnly: false, diagnostics: diagnostics);
return false;
}
// lookupResult.IsSingleViable above guaranteed there is exactly one symbol.
Symbol lookupSymbol = lookupResult.SingleSymbolOrDefault;
Debug.Assert((object)lookupSymbol != null);
if (lookupSymbol.IsStatic || lookupSymbol.DeclaredAccessibility != Accessibility.Public || lookupSymbol.Kind != SymbolKind.Property)
{
return false;
}
// NOTE: accessor can be inherited from overridden property
MethodSymbol currentPropertyGetterCandidate = ((PropertySymbol)lookupSymbol).GetOwnOrInheritedGetMethod();
if ((object)currentPropertyGetterCandidate == null)
{
return false;
}
else
{
bool isAccessible = this.IsAccessible(currentPropertyGetterCandidate, ref useSiteInfo);
diagnostics.Add(collectionSyntax, useSiteInfo);
if (!isAccessible)
{
// NOTE: per Dev10 and the spec, the property has to be public, but the accessor just has to be accessible
return false;
}
}
builder.CurrentPropertyGetter = currentPropertyGetterCandidate;
lookupResult.Clear(); // Reuse the same LookupResult
MethodArgumentInfo moveNextMethodCandidate = FindForEachPatternMethod(syntax, collectionSyntax, enumeratorType,
isAsync ? WellKnownMemberNames.MoveNextAsyncMethodName : WellKnownMemberNames.MoveNextMethodName,
lookupResult, warningsOnly: false, diagnostics, isAsync);
if ((object)moveNextMethodCandidate == null ||
moveNextMethodCandidate.Method.IsStatic || moveNextMethodCandidate.Method.DeclaredAccessibility != Accessibility.Public ||
IsInvalidMoveNextMethod(moveNextMethodCandidate.Method, isAsync))
{
return false;
}
builder.MoveNextInfo = moveNextMethodCandidate;
return true;
}
finally
{
lookupResult.Free();
}
}
private bool IsInvalidMoveNextMethod(MethodSymbol moveNextMethodCandidate, bool isAsync)
{
if (isAsync)
{
// We'll verify the return type from `MoveNextAsync` when we try to bind the `await` for it
return false;
}
// SPEC VIOLATION: Dev10 checks the return type of the original definition, rather than the return type of the actual method.
return moveNextMethodCandidate.OriginalDefinition.ReturnType.SpecialType != SpecialType.System_Boolean;
}
private void ReportEnumerableWarning(SyntaxNode collectionSyntax, BindingDiagnosticBag diagnostics, TypeSymbol enumeratorType, Symbol patternMemberCandidate)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
if (this.IsAccessible(patternMemberCandidate, ref useSiteInfo))
{
diagnostics.Add(ErrorCode.WRN_PatternBadSignature, collectionSyntax.Location, enumeratorType, MessageID.IDS_Collection.Localize(), patternMemberCandidate);
}
diagnostics.Add(collectionSyntax, useSiteInfo);
}
internal static bool IsIEnumerable(TypeSymbol type)
{
switch (((TypeSymbol)type.OriginalDefinition).SpecialType)
{
case SpecialType.System_Collections_IEnumerable:
case SpecialType.System_Collections_Generic_IEnumerable_T:
return true;
default:
return false;
}
}
private bool IsIAsyncEnumerable(TypeSymbol type)
{
return type.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T));
}
/// <summary>
/// Checks if the given type implements (or extends, in the case of an interface),
/// System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T>,
/// (or System.Collections.Generic.IAsyncEnumerable<T>)
/// for at least one T.
/// </summary>
/// <param name="builder">builder to fill in CollectionType.</param>
/// <param name="type">Type to check.</param>
/// <param name="diagnostics" />
/// <param name="foundMultiple">True if multiple T's are found.</param>
/// <returns>True if some IEnumerable is found (may still be ambiguous).</returns>
private bool AllInterfacesContainsIEnumerable(
SyntaxNode collectionSyntax,
ref ForEachEnumeratorInfo.Builder builder,
TypeSymbol type,
bool isAsync,
BindingDiagnosticBag diagnostics,
out bool foundMultiple)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
NamedTypeSymbol implementedIEnumerable = GetIEnumerableOfT(type, isAsync, Compilation, ref useSiteInfo, out foundMultiple, out bool needSupportForRefStructInterfaces);
// Prefer generic to non-generic, unless it is inaccessible.
if (((object)implementedIEnumerable == null) || !this.IsAccessible(implementedIEnumerable, ref useSiteInfo))
{
implementedIEnumerable = null;
if (!isAsync)
{
var implementedNonGeneric = this.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable);
if ((object)implementedNonGeneric != null &&
this.Conversions.HasImplicitConversionToOrImplementsVarianceCompatibleInterface(type, implementedNonGeneric, ref useSiteInfo, out needSupportForRefStructInterfaces))
{
implementedIEnumerable = implementedNonGeneric;
}
}
}
if (implementedIEnumerable is not null && needSupportForRefStructInterfaces && type.ContainingModule != Compilation.SourceModule)
{
CheckFeatureAvailability(collectionSyntax, MessageID.IDS_FeatureRefStructInterfaces, diagnostics);
}
diagnostics.Add(collectionSyntax, useSiteInfo);
builder.CollectionType = implementedIEnumerable;
return (object)implementedIEnumerable != null;
}
internal static NamedTypeSymbol GetIEnumerableOfT(
TypeSymbol type, bool isAsync, CSharpCompilation compilation, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
out bool foundMultiple, out bool needSupportForRefStructInterfaces)
{
NamedTypeSymbol implementedIEnumerable = null;
foundMultiple = false;
if (type.TypeKind == TypeKind.TypeParameter)
{
var typeParameter = (TypeParameterSymbol)type;
needSupportForRefStructInterfaces = typeParameter.AllowsRefLikeType;
var allInterfaces = typeParameter.EffectiveBaseClass(ref useSiteInfo).AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo)
.Concat(typeParameter.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo));
GetIEnumerableOfT(allInterfaces, isAsync, compilation, ref @implementedIEnumerable, ref foundMultiple);
}
else
{
needSupportForRefStructInterfaces = type.IsRefLikeType;
GetIEnumerableOfT(type.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo), isAsync, compilation, ref @implementedIEnumerable, ref foundMultiple);
}
return implementedIEnumerable;
}
private static void GetIEnumerableOfT(ImmutableArray<NamedTypeSymbol> interfaces, bool isAsync, CSharpCompilation compilation, ref NamedTypeSymbol result, ref bool foundMultiple)
{
if (foundMultiple)
{
return;
}
interfaces = MethodTypeInferrer.ModuloReferenceTypeNullabilityDifferences(interfaces, VarianceKind.In);
foreach (NamedTypeSymbol @interface in interfaces)
{
if (IsIEnumerableT(@interface.OriginalDefinition, isAsync, compilation))
{
if ((object)result == null ||
TypeSymbol.Equals(@interface, result, TypeCompareKind.IgnoreTupleNames))
{
result = @interface;
}
else
{
foundMultiple = true;
return;
}
}
}
}
internal static bool IsIEnumerableT(TypeSymbol type, bool isAsync, CSharpCompilation compilation)
{
if (isAsync)
{
return type.Equals(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T));
}
else
{
return type.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T;
}
}
/// <summary>
/// Report appropriate diagnostics when lookup of a pattern member (i.e. GetEnumerator, Current, or MoveNext) fails.
/// </summary>
/// <param name="lookupResult">Failed lookup result.</param>
/// <param name="patternType">Type in which member was looked up.</param>
/// <param name="memberName">Name of looked up member.</param>
/// <param name="warningsOnly">True if failures should result in warnings; false if they should result in errors.</param>
/// <param name="diagnostics">Populated appropriately.</param>
private void ReportPatternMemberLookupDiagnostics(SyntaxNode collectionSyntax, LookupResult lookupResult, TypeSymbol patternType, string memberName, bool warningsOnly, BindingDiagnosticBag diagnostics)
{
if (lookupResult.Symbols.Any())
{
if (warningsOnly)
{
ReportEnumerableWarning(collectionSyntax, diagnostics, patternType, lookupResult.Symbols.First());
}
else
{
lookupResult.Clear();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupMembersInType(
lookupResult,
patternType,
memberName,
arity: 0,
basesBeingResolved: null,
options: LookupOptions.Default,
originalBinder: this,
diagnose: true,
useSiteInfo: ref useSiteInfo);
diagnostics.Add(collectionSyntax, useSiteInfo);
if (lookupResult.Error != null)
{
diagnostics.Add(lookupResult.Error, collectionSyntax.Location);
}
}
}
else if (!warningsOnly)
{
diagnostics.Add(ErrorCode.ERR_NoSuchMember, collectionSyntax.Location, patternType, memberName);
}
}
private MethodArgumentInfo GetParameterlessSpecialTypeMemberInfo(SpecialMember member, SyntaxNode syntax, BindingDiagnosticBag diagnostics)
{
var resolvedMember = (MethodSymbol)GetSpecialTypeMember(member, diagnostics, syntax);
Debug.Assert(resolvedMember is null or { ParameterCount: 0 });
return resolvedMember is not null
? MethodArgumentInfo.CreateParameterlessMethod(resolvedMember)
: null;
}
/// <param name="extensionReceiverOpt">If method is an extension method, this must be non-null.</param>
private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpression extensionReceiverOpt, bool expanded, SyntaxNode syntax, BindingDiagnosticBag diagnostics)
{
Debug.Assert((extensionReceiverOpt != null) == method.IsExtensionMethod);
if (method.ParameterCount == 0)
{
return MethodArgumentInfo.CreateParameterlessMethod(method);
}
var argsBuilder = ArrayBuilder<BoundExpression>.GetInstance(method.ParameterCount);
if (method.IsExtensionMethod)
{
argsBuilder.Add(extensionReceiverOpt);
}
ImmutableArray<int> argsToParams = default;
BindDefaultArguments(
syntax,
method.Parameters,
argsBuilder,
argumentRefKindsBuilder: null,
namesBuilder: null,
ref argsToParams,
defaultArguments: out BitVector defaultArguments,
expanded,
enableCallerInfo: true,
diagnostics);
Debug.Assert(argsToParams.IsDefault);
return new MethodArgumentInfo(method, argsBuilder.ToImmutableAndFree(), defaultArguments, expanded);
}
}
}
|