public BoundForEachStatement(SyntaxNode syntax, ForEachEnumeratorInfo enumeratorInfoOpt, Conversion elementConversion, BoundTypeExpression iterationVariableType, ImmutableArray <LocalSymbol> iterationVariables, BoundExpression expression, BoundForEachDeconstructStep deconstructionOpt, BoundStatement body, bool @checked, GeneratedLabelSymbol breakLabel, GeneratedLabelSymbol continueLabel, bool hasErrors = false) : this(syntax, enumeratorInfoOpt, elementConversion, iterationVariableType, iterationVariables, iterationErrorExpressionOpt : null, expression, deconstructionOpt, body, @checked, breakLabel, continueLabel, hasErrors) { }
/// <summary> /// Checks if the given type implements (or extends, in the case of an interface), /// System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<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( ref ForEachEnumeratorInfo.Builder builder, TypeSymbol type, DiagnosticBag diagnostics, out bool foundMultiple) { Debug.Assert(!IsIEnumerable(type)); NamedTypeSymbol implementedIEnumerable = null; foundMultiple = false; HashSet<DiagnosticInfo> useSiteDiagnostics = null; if (type.TypeKind == TypeKind.TypeParameter) { var typeParameter = (TypeParameterSymbol)type; GetIEnumerableOfT(typeParameter.EffectiveBaseClass(ref useSiteDiagnostics).AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), ref @implementedIEnumerable, ref foundMultiple); GetIEnumerableOfT(typeParameter.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), ref @implementedIEnumerable, ref foundMultiple); } else { GetIEnumerableOfT(type.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), ref @implementedIEnumerable, ref foundMultiple); } // Prefer generic to non-generic, unless it is inaccessible. if (((object)implementedIEnumerable == null) || !this.IsAccessible(implementedIEnumerable, ref useSiteDiagnostics)) { implementedIEnumerable = null; var implementedNonGeneric = this.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable); if ((object)implementedNonGeneric != null) { var conversion = this.Conversions.ClassifyImplicitConversion(type, implementedNonGeneric, ref useSiteDiagnostics); if (conversion.IsImplicit) { implementedIEnumerable = implementedNonGeneric; } } } diagnostics.Add(_syntax.Expression, useSiteDiagnostics); builder.CollectionType = implementedIEnumerable; return (object)implementedIEnumerable != null; }
/// <summary> /// Lower a foreach loop that will enumerate a collection using an enumerator. /// /// E e = ((C)(x)).GetEnumerator() /// try { /// while (e.MoveNext()) { /// V v = (V)(T)e.Current; /// // body /// } /// } /// finally { /// // clean up e /// } /// </summary> private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement node) { ForEachStatementSyntax forEachSyntax = (ForEachStatementSyntax)node.Syntax; ForEachEnumeratorInfo enumeratorInfo = node.EnumeratorInfoOpt; Debug.Assert(enumeratorInfo != null); BoundExpression collectionExpression = GetUnconvertedCollectionExpression(node); BoundExpression rewrittenExpression = (BoundExpression)Visit(collectionExpression); BoundStatement rewrittenBody = (BoundStatement)Visit(node.Body); TypeSymbol enumeratorType = enumeratorInfo.GetEnumeratorMethod.ReturnType; TypeSymbol elementType = enumeratorInfo.ElementType; // E e LocalSymbol enumeratorVar = _factory.SynthesizedLocal(enumeratorType, syntax: forEachSyntax, kind: SynthesizedLocalKind.ForEachEnumerator); // Reference to e. BoundLocal boundEnumeratorVar = MakeBoundLocal(forEachSyntax, enumeratorVar, enumeratorType); // ((C)(x)).GetEnumerator() or (x).GetEnumerator(); BoundExpression enumeratorVarInitValue = SynthesizeCall(forEachSyntax, rewrittenExpression, enumeratorInfo.GetEnumeratorMethod, enumeratorInfo.CollectionConversion, enumeratorInfo.CollectionType); // E e = ((C)(x)).GetEnumerator(); BoundStatement enumeratorVarDecl = MakeLocalDeclaration(forEachSyntax, enumeratorVar, enumeratorVarInitValue); AddForEachExpressionSequencePoint(forEachSyntax, ref enumeratorVarDecl); // V v LocalSymbol iterationVar = node.IterationVariable; //(V)(T)e.Current BoundExpression iterationVarAssignValue = MakeConversion( syntax: forEachSyntax, rewrittenOperand: MakeConversion( syntax: forEachSyntax, rewrittenOperand: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.CurrentPropertyGetter), conversion: enumeratorInfo.CurrentConversion, rewrittenType: elementType, @checked: node.Checked), conversion: node.ElementConversion, rewrittenType: iterationVar.Type, @checked: node.Checked); // V v = (V)(T)e.Current; BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarAssignValue); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* node.Body */ // } var rewrittenBodyBlock = CreateBlockDeclaringIterationVariable(iterationVar, iterationVarDecl, rewrittenBody, forEachSyntax); BoundStatement whileLoop = RewriteWhileStatement( syntax: forEachSyntax, rewrittenCondition: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.MoveNextMethod), conditionSequencePointSpan: forEachSyntax.InKeyword.Span, rewrittenBody: rewrittenBodyBlock, breakLabel: node.BreakLabel, continueLabel: node.ContinueLabel, hasErrors: false); BoundStatement result; MethodSymbol disposeMethod; if (enumeratorInfo.NeedsDisposeMethod && Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, forEachSyntax, _diagnostics, out disposeMethod)) { Binder.ReportDiagnosticsIfObsolete(_diagnostics, disposeMethod, forEachSyntax, hasBaseReceiver: false, containingMember: _factory.CurrentMethod, containingType: _factory.CurrentType, location: enumeratorInfo.Location); BoundBlock finallyBlockOpt; var idisposableTypeSymbol = disposeMethod.ContainingType; var conversions = new TypeConversions(_factory.CurrentMethod.ContainingAssembly.CorLibrary); HashSet <DiagnosticInfo> useSiteDiagnostics = null; var isImplicit = conversions.ClassifyImplicitConversion(enumeratorType, idisposableTypeSymbol, ref useSiteDiagnostics).IsImplicit; _diagnostics.Add(forEachSyntax, useSiteDiagnostics); if (isImplicit) { Debug.Assert(enumeratorInfo.NeedsDisposeMethod); Conversion receiverConversion = enumeratorType.IsStructType() ? Conversion.Boxing : Conversion.ImplicitReference; // ((IDisposable)e).Dispose(); or e.Dispose(); BoundStatement disposeCall = new BoundExpressionStatement(forEachSyntax, expression: SynthesizeCall(forEachSyntax, boundEnumeratorVar, disposeMethod, receiverConversion, idisposableTypeSymbol)); BoundStatement disposeStmt; if (enumeratorType.IsValueType) { // No way for the struct to be nullable and disposable. Debug.Assert(((TypeSymbol)enumeratorType.OriginalDefinition).SpecialType != SpecialType.System_Nullable_T); // For non-nullable structs, no null check is required. disposeStmt = disposeCall; } else { // NB: cast to object missing from spec. Needed to ignore user-defined operators and box type parameters. // if ((object)e != null) ((IDisposable)e).Dispose(); disposeStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, left: MakeConversion( syntax: forEachSyntax, rewrittenOperand: boundEnumeratorVar, conversion: enumeratorInfo.EnumeratorConversion, rewrittenType: _compilation.GetSpecialType(SpecialType.System_Object), @checked: false), right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: _compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: disposeCall, rewrittenAlternativeOpt: null, hasErrors: false); } finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray <LocalSymbol> .Empty, localFunctions: ImmutableArray <LocalFunctionSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(disposeStmt)); } else { Debug.Assert(!enumeratorType.IsSealed); // IDisposable d LocalSymbol disposableVar = _factory.SynthesizedLocal(idisposableTypeSymbol); // Reference to d. BoundLocal boundDisposableVar = MakeBoundLocal(forEachSyntax, disposableVar, idisposableTypeSymbol); BoundTypeExpression boundIDisposableTypeExpr = new BoundTypeExpression(forEachSyntax, aliasOpt: null, type: idisposableTypeSymbol); // e as IDisposable BoundExpression disposableVarInitValue = new BoundAsOperator(forEachSyntax, operand: boundEnumeratorVar, targetType: boundIDisposableTypeExpr, conversion: Conversion.ExplicitReference, // Explicit so the emitter won't optimize it away. type: idisposableTypeSymbol); // IDisposable d = e as IDisposable; BoundStatement disposableVarDecl = MakeLocalDeclaration(forEachSyntax, disposableVar, disposableVarInitValue); // if (d != null) d.Dispose(); BoundStatement ifStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, // reference equality left: boundDisposableVar, right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: _compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: new BoundExpressionStatement(forEachSyntax, expression: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundDisposableVar, method: disposeMethod)), rewrittenAlternativeOpt: null, hasErrors: false); // IDisposable d = e as IDisposable; // if (d != null) d.Dispose(); finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray.Create <LocalSymbol>(disposableVar), localFunctions: ImmutableArray <LocalFunctionSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(disposableVarDecl, ifStmt)); } // try { // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } // } // finally { // /* dispose of e */ // } BoundStatement tryFinally = new BoundTryStatement(forEachSyntax, tryBlock: new BoundBlock(forEachSyntax, locals: ImmutableArray <LocalSymbol> .Empty, localFunctions: ImmutableArray <LocalFunctionSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(whileLoop)), catchBlocks: ImmutableArray <BoundCatchBlock> .Empty, finallyBlockOpt: finallyBlockOpt); // E e = ((C)(x)).GetEnumerator(); // try { // /* as above */ result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), localFunctions: ImmutableArray <LocalFunctionSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(enumeratorVarDecl, tryFinally)); } else { // E e = ((C)(x)).GetEnumerator(); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), localFunctions: ImmutableArray <LocalFunctionSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(enumeratorVarDecl, whileLoop)); } AddForEachKeywordSequencePoint(forEachSyntax, ref result); return(result); }
/// <summary> /// Called after it is determined that the expression being enumerated is of a type that /// has a GetEnumerator method. Checks to see if the return type of the GetEnumerator /// method is suitable (i.e. has Current and MoveNext). /// </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(ref ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics) { Debug.Assert((object)builder.GetEnumeratorMethod != null); MethodSymbol getEnumeratorMethod = builder.GetEnumeratorMethod; 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). HashSet<DiagnosticInfo> useSiteDiagnostics = null; this.LookupMembersInType( lookupResult, enumeratorType, CurrentPropertyName, arity: 0, basesBeingResolved: null, options: LookupOptions.Default, // properties are not invocable - their accessors are originalBinder: this, diagnose: false, useSiteDiagnostics: ref useSiteDiagnostics); diagnostics.Add(_syntax.Expression, useSiteDiagnostics); useSiteDiagnostics = null; if (!lookupResult.IsSingleViable) { ReportPatternMemberLookupDiagnostics(lookupResult, enumeratorType, 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 useSiteDiagnostics); diagnostics.Add(_syntax.Expression, useSiteDiagnostics); 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 MethodSymbol moveNextMethodCandidate = FindForEachPatternMethod(enumeratorType, MoveNextMethodName, lookupResult, warningsOnly: false, diagnostics: diagnostics); // SPEC VIOLATION: Dev10 checks the return type of the original definition, rather than the return type of the actual method. if ((object)moveNextMethodCandidate == null || moveNextMethodCandidate.IsStatic || moveNextMethodCandidate.DeclaredAccessibility != Accessibility.Public || ((MethodSymbol)moveNextMethodCandidate.OriginalDefinition).ReturnType.SpecialType != SpecialType.System_Boolean) { return false; } builder.MoveNextMethod = moveNextMethodCandidate; return true; } finally { lookupResult.Free(); } }
/// <summary> /// Check for a GetEnumerator 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="collectionExprType">Type of the 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.GetEnumeratorMethod"/> 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(ref ForEachEnumeratorInfo.Builder builder, TypeSymbol collectionExprType, DiagnosticBag diagnostics) { LookupResult lookupResult = LookupResult.GetInstance(); MethodSymbol getEnumeratorMethod = FindForEachPatternMethod(collectionExprType, GetEnumeratorMethodName, lookupResult, warningsOnly: true, diagnostics: diagnostics); lookupResult.Free(); builder.GetEnumeratorMethod = getEnumeratorMethod; return (object)getEnumeratorMethod != null; }
private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumeratorInfo.Builder builder, DiagnosticBag 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.ElementType = _syntax.Type.IsVar ? (TypeSymbol)DynamicTypeSymbol.Instance : GetSpecialType(SpecialType.System_Object, diagnostics, _syntax); } else { builder.ElementType = collectionExprType.SpecialType == SpecialType.System_String? GetSpecialType(SpecialType.System_Char, diagnostics, _syntax) : ((ArrayTypeSymbol)collectionExprType).ElementType; } // 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.GetEnumeratorMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerable__GetEnumerator, diagnostics, _syntax); builder.CurrentPropertyGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__get_Current, diagnostics, _syntax); builder.MoveNextMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__MoveNext, diagnostics, _syntax); Debug.Assert((object)builder.GetEnumeratorMethod == null || builder.GetEnumeratorMethod.ReturnType == this.Compilation.GetSpecialType(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.NeedsDisposeMethod = true; return builder; }
/// <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 difference. 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 method of the collection type (return type will be the enumerator type from the spec) /// 4) Current property of the enumerator type /// 5) MoveNext 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(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, DiagnosticBag diagnostics) { TypeSymbol collectionExprType = collectionExpr.Type; if (collectionExpr.ConstantValue != null && collectionExpr.ConstantValue.IsNull) { // Spec seems to refer to null literals, but Dev10 reports anything known to be null. Debug.Assert(collectionExpr.ConstantValue.IsNull); // only constant value with no type diagnostics.Add(ErrorCode.ERR_NullNotValid, _syntax.Expression.Location); return false; } if ((object)collectionExprType == null) // There's no way to enumerate something without a type. { // The null literal was caught above, so anything else with a null type is a method group or anonymous function diagnostics.Add(ErrorCode.ERR_AnonMethGrpInForEach, _syntax.Expression.Location, collectionExpr.Display); // CONSIDER: dev10 also reports ERR_ForEachMissingMember (i.e. failed pattern match). return false; } 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 false; } // The spec specifically lists the collection, enumerator, and element types for arrays and dynamic. if (collectionExprType.Kind == SymbolKind.ArrayType || collectionExprType.Kind == SymbolKind.DynamicType) { builder = GetDefaultEnumeratorInfo(builder, diagnostics, collectionExprType); return true; } bool foundMultipleGenericIEnumerableInterfaces; if (SatisfiesGetEnumeratorPattern(ref builder, collectionExprType, diagnostics)) { Debug.Assert((object)builder.GetEnumeratorMethod != null); builder.CollectionType = collectionExprType; if (SatisfiesForEachPattern(ref builder, diagnostics)) { builder.ElementType = ((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol).Type; // 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. var useSiteDiagnosticBag = DiagnosticBag.GetInstance(); TypeSymbol enumeratorType = builder.GetEnumeratorMethod.ReturnType; HashSet<DiagnosticInfo> useSiteDiagnostics = null; if (!enumeratorType.IsSealed || this.Conversions.ClassifyImplicitConversion(enumeratorType, this.Compilation.GetSpecialType(SpecialType.System_IDisposable), ref useSiteDiagnostics).IsImplicit) { builder.NeedsDisposeMethod = true; diagnostics.AddRange(useSiteDiagnosticBag); } useSiteDiagnosticBag.Free(); diagnostics.Add(_syntax, useSiteDiagnostics); return true; } MethodSymbol getEnumeratorMethod = builder.GetEnumeratorMethod; diagnostics.Add(ErrorCode.ERR_BadGetEnumerator, _syntax.Expression.Location, getEnumeratorMethod.ReturnType, getEnumeratorMethod); return false; } if (IsIEnumerable(collectionExprType)) { // This indicates a problem with the special IEnumerable type - it should have satisfied the GetEnumerator pattern. diagnostics.Add(ErrorCode.ERR_ForEachMissingMember, _syntax.Expression.Location, collectionExprType, GetEnumeratorMethodName); return false; } if (AllInterfacesContainsIEnumerable(ref builder, collectionExprType, diagnostics, out foundMultipleGenericIEnumerableInterfaces)) { CSharpSyntaxNode errorLocationSyntax = _syntax.Expression; if (foundMultipleGenericIEnumerableInterfaces) { diagnostics.Add(ErrorCode.ERR_MultipleIEnumOfT, errorLocationSyntax.Location, collectionExprType, this.Compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T)); return false; } Debug.Assert((object)builder.CollectionType != null); NamedTypeSymbol collectionType = (NamedTypeSymbol)builder.CollectionType; if (collectionType.IsGenericType) { // If the type is generic, we have to search for the methods Debug.Assert(collectionType.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T); builder.ElementType = collectionType.TypeArgumentsNoUseSiteDiagnostics.Single(); MethodSymbol getEnumeratorMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_Generic_IEnumerable_T__GetEnumerator, diagnostics, errorLocationSyntax); if ((object)getEnumeratorMethod != null) { builder.GetEnumeratorMethod = getEnumeratorMethod.AsMember(collectionType); TypeSymbol enumeratorType = builder.GetEnumeratorMethod.ReturnType; Debug.Assert(enumeratorType.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerator_T); MethodSymbol currentPropertyGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_Generic_IEnumerator_T__get_Current, diagnostics, errorLocationSyntax); if ((object)currentPropertyGetter != null) { builder.CurrentPropertyGetter = currentPropertyGetter.AsMember((NamedTypeSymbol)enumeratorType); } } builder.MoveNextMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__MoveNext, diagnostics, errorLocationSyntax); // NOTE: MoveNext is actually inherited from System.Collections.IEnumerator } else { // Non-generic - use special members to avoid re-computing Debug.Assert(collectionType.SpecialType == SpecialType.System_Collections_IEnumerable); builder.ElementType = GetSpecialType(SpecialType.System_Object, diagnostics, errorLocationSyntax); builder.GetEnumeratorMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerable__GetEnumerator, diagnostics, errorLocationSyntax); builder.CurrentPropertyGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__get_Current, diagnostics, errorLocationSyntax); builder.MoveNextMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__MoveNext, diagnostics, errorLocationSyntax); Debug.Assert((object)builder.GetEnumeratorMethod == null || builder.GetEnumeratorMethod.ReturnType == GetSpecialType(SpecialType.System_Collections_IEnumerator, diagnostics, errorLocationSyntax)); } // 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.NeedsDisposeMethod = true; return true; } // 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 (collectionExprType.SpecialType == SpecialType.System_String) { builder = GetDefaultEnumeratorInfo(builder, diagnostics, collectionExprType); return true; } if (!string.IsNullOrEmpty(collectionExprType.Name) || !collectionExpr.HasErrors) { diagnostics.Add(ErrorCode.ERR_ForEachMissingMember, _syntax.Expression.Location, collectionExprType.ToDisplayString(), GetEnumeratorMethodName); } return false; }
private bool GetEnumeratorInfoAndInferCollectionElementType(ref ForEachEnumeratorInfo.Builder builder, ref BoundExpression collectionExpr, DiagnosticBag diagnostics, out TypeSymbol inferredType) { UnwrapCollectionExpressionIfNullable(ref collectionExpr, diagnostics); bool gotInfo = GetEnumeratorInfo(ref builder, collectionExpr, diagnostics); if (!gotInfo) { inferredType = null; } else if (collectionExpr.HasDynamicType()) { // If the enumerator is dynamic, it yields dynamic values inferredType = 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 = GetSpecialType(SpecialType.System_Char, diagnostics, collectionExpr.Syntax); } else { inferredType = builder.ElementType; } return gotInfo; }