/// <remarks> /// From ExpressionBinder::EnsureQMarkTypesCompatible: /// /// The v2.0 specification states that the types of the second and third operands T and S of a ternary operator /// must be TT and TS such that either (a) TT==TS, or (b), TT->TS or TS->TT but not both. /// /// Unfortunately that is not what we implemented in v2.0. Instead, we implemented /// that either (a) TT=TS or (b) T->TS or S->TT but not both. That is, we looked at the /// convertibility of the expressions, not the types. /// /// /// Changing that to the algorithm in the standard would be a breaking change. /// /// b ? (Func<int>)(delegate(){return 1;}) : (delegate(){return 2;}) /// /// and /// /// b ? 0 : myenum /// /// would suddenly stop working. (The first because o2 has no type, the second because 0 goes to /// any enum but enum doesn't go to int.) /// /// It gets worse. We would like the 3.0 language features which require type inference to use /// a consistent algorithm, and that furthermore, the algorithm be smart about choosing the best /// of a set of types. However, the language committee has decided that this algorithm will NOT /// consume information about the convertibility of expressions. Rather, it will gather up all /// the possible types and then pick the "largest" of them. /// /// To maintain backwards compatibility while still participating in the spirit of consistency, /// we implement an algorithm here which picks the type based on expression convertibility, but /// if there is a conflict, then it chooses the larger type rather than producing a type error. /// This means that b?0:myshort will have type int rather than producing an error (because 0->short, /// myshort->int). /// </remarks> private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node, DiagnosticBag diagnostics) { BoundExpression condition = BindBooleanExpression(node.Condition, diagnostics); BoundExpression trueExpr = BindValue(node.WhenTrue, diagnostics, BindValueKind.RValue); BoundExpression falseExpr = BindValue(node.WhenFalse, diagnostics, BindValueKind.RValue); TypeSymbol trueType = trueExpr.Type; TypeSymbol falseType = falseExpr.Type; TypeSymbol type; bool hasErrors = false; if (trueType == falseType) { // NOTE: Dev10 lets the type inferrer handle this case (presumably, for maximum consistency), // but it seems like a worthwhile short-circuit for a common case. if ((object)trueType == null) { // If trueExpr and falseExpr both have type null, then we don't have any symbols // to pass to a SymbolDistinguisher (which ERR_InvalidQM would usually require). diagnostics.Add(ErrorCode.ERR_InvalidQM, node.Location, trueExpr.Display, falseExpr.Display); type = CreateErrorType(); hasErrors = true; } else { // <expr> ? T : T type = trueType; } } else { bool hadMultipleCandidates; HashSet<DiagnosticInfo> useSiteDiagnostics = null; TypeSymbol bestType = BestTypeInferrer.InferBestTypeForConditionalOperator(trueExpr, falseExpr, this.Conversions, out hadMultipleCandidates, ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); if ((object)bestType == null) { // CONSIDER: Dev10 suppresses ERR_InvalidQM unless the following is true for both trueType and falseType // (!T->type->IsErrorType() || T->type->AsErrorType()->HasTypeParent() || T->type->AsErrorType()->HasNSParent()) if (hadMultipleCandidates) { diagnostics.Add(ErrorCode.ERR_AmbigQM, node.Location, trueExpr.Display, falseExpr.Display); } else { object trueArg = trueExpr.Display; object falseArg = falseExpr.Display; Symbol trueSymbol = trueArg as Symbol; Symbol falseSymbol = falseArg as Symbol; if ((object)trueSymbol != null && (object)falseSymbol != null) { SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, trueSymbol, falseSymbol); trueArg = distinguisher.First; falseArg = distinguisher.Second; } diagnostics.Add(ErrorCode.ERR_InvalidQM, node.Location, trueArg, falseArg); } type = CreateErrorType(); hasErrors = true; } else if (bestType.IsErrorType()) { type = bestType; hasErrors = true; } else { trueExpr = GenerateConversionForAssignment(bestType, trueExpr, diagnostics); falseExpr = GenerateConversionForAssignment(bestType, falseExpr, diagnostics); if (trueExpr.HasAnyErrors || falseExpr.HasAnyErrors) { // If one of the conversions went wrong (e.g. return type of method group being converted // didn't match), then we don't want to use bestType because it's not accurate. type = CreateErrorType(); hasErrors = true; } else { type = bestType; } } } ConstantValue constantValue = null; if (!hasErrors) { constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr); hasErrors = constantValue != null && constantValue.IsBad; } return new BoundConditionalOperator(node, condition, trueExpr, falseExpr, constantValue, type, hasErrors); }
private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics) { BoundExpression collectionExpr = this.Next.BindValue(syntax.Expression, diagnostics, BindValueKind.RValue); //bind with next to avoid seeing iteration variable ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); TypeSymbol inferredType; bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); // These should only occur when special types are missing or malformed. hasErrors = hasErrors || (object)builder.GetEnumeratorMethod == null || (object)builder.MoveNextMethod == null || (object)builder.CurrentPropertyGetter == null; // Check for local variable conflicts in the *enclosing* binder; obviously the *current* // binder has a local that matches! hasErrors |= this.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 = this.syntax.Type; bool isVar; AliasSymbol alias; TypeSymbol declType = BindType(typeSyntax, diagnostics, out isVar, out alias); TypeSymbol iterationVariableType; if (isVar) { iterationVariableType = inferredType ?? CreateErrorType("var"); } else { Debug.Assert((object)declType != null); iterationVariableType = declType; } BoundTypeExpression boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); this.IterationVariable.SetTypeSymbol(iterationVariableType); BoundStatement body = BindPossibleEmbeddedStatement(syntax.Statement, diagnostics); hasErrors = hasErrors || iterationVariableType.IsErrorType(); // Skip the conversion checks and array/enumerator differentiation if we know we have an error. if (hasErrors) { return(new BoundForEachStatement( syntax, ImmutableArray <LocalSymbol> .Empty, null, // can't be sure that it's complete default(Conversion), boundIterationVariableType, this.IterationVariable, collectionExpr, body, CheckOverflowAtRuntime, this.BreakLabel, this.ContinueLabel, hasErrors)); } var foreachKeyword = syntax.ForEachKeyword; ReportDiagnosticsIfObsolete(diagnostics, builder.GetEnumeratorMethod, foreachKeyword, hasBaseReceiver: false); ReportDiagnosticsIfObsolete(diagnostics, builder.MoveNextMethod, 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). HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion elementConversion = this.Conversions.ClassifyConversionForCast(inferredType, iterationVariableType, ref useSiteDiagnostics); if (!elementConversion.IsValid) { ImmutableArray <MethodSymbol> originalUserDefinedConversions = elementConversion.OriginalUserDefinedConversions; if (originalUserDefinedConversions.Length > 1) { diagnostics.Add(ErrorCode.ERR_AmbigUDConv, syntax.ForEachKeyword.GetLocation(), originalUserDefinedConversions[0], originalUserDefinedConversions[1], inferredType, iterationVariableType); } else { SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType, iterationVariableType); diagnostics.Add(ErrorCode.ERR_NoExplicitConv, syntax.ForEachKeyword.GetLocation(), distinguisher.First, distinguisher.Second); } hasErrors = true; } else { ReportDiagnosticsIfObsolete(diagnostics, elementConversion, syntax.ForEachKeyword, hasBaseReceiver: false); } // 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). builder.CollectionConversion = this.Conversions.ClassifyConversionFromExpression(collectionExpr, builder.CollectionType, ref useSiteDiagnostics); builder.CurrentConversion = this.Conversions.ClassifyConversion(builder.CurrentPropertyGetter.ReturnType, builder.ElementType, ref useSiteDiagnostics); builder.EnumeratorConversion = this.Conversions.ClassifyConversion(builder.GetEnumeratorMethod.ReturnType, GetSpecialType(SpecialType.System_Object, diagnostics, this.syntax), ref useSiteDiagnostics); diagnostics.Add(syntax.ForEachKeyword.GetLocation(), useSiteDiagnostics); // 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(builder.CollectionConversion.IsValid); Debug.Assert(builder.CurrentConversion.IsValid || (builder.ElementType.IsPointerType() && collectionExpr.Type.IsArray()) || (builder.ElementType.IsNullableType() && builder.ElementType.GetMemberTypeArgumentsNoUseSiteDiagnostics().Single().IsErrorType() && collectionExpr.Type.IsArray())); Debug.Assert(builder.EnumeratorConversion.IsValid || this.Compilation.GetSpecialType(SpecialType.System_Object).TypeKind == TypeKind.Error, "Conversions to object succeed unless there's a problem with the object type"); // If user-defined conversions could occur here, we would need to check for ObsoleteAttribute. Debug.Assert((object)builder.CollectionConversion.Method == null, "Conversion from collection expression to collection type should not be user-defined"); Debug.Assert((object)builder.CurrentConversion.Method == null, "Conversion from Current property type to element type should not be user-defined"); Debug.Assert((object)builder.EnumeratorConversion.Method == null, "Conversion from GetEnumerator return type to System.Object should not be user-defined"); // 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. BoundConversion convertedCollectionExpression = new BoundConversion( collectionExpr.Syntax, collectionExpr, builder.CollectionConversion, CheckOverflowAtRuntime, false, ConstantValue.NotAvailable, builder.CollectionType); return(new BoundForEachStatement( syntax, ImmutableArray <LocalSymbol> .Empty, builder.Build(this.Flags), elementConversion, boundIterationVariableType, this.IterationVariable, convertedCollectionExpression, body, CheckOverflowAtRuntime, this.BreakLabel, this.ContinueLabel, hasErrors)); }
private static bool ReportAsOperatorConversionDiagnostics( CSharpSyntaxNode node, DiagnosticBag diagnostics, Compilation compilation, TypeSymbol operandType, TypeSymbol targetType, ConversionKind conversionKind, ConstantValue operandConstantValue) { // SPEC: In an operation of the form E as T, E must be an expression and T must be a reference type, // SPEC: a type parameter known to be a reference type, or a nullable type. // SPEC: Furthermore, at least one of the following must be true, or otherwise a compile-time error occurs: // SPEC: • An identity (§6.1.1), implicit nullable (§6.1.4), implicit reference (§6.1.6), boxing (§6.1.7), // SPEC: explicit nullable (§6.2.3), explicit reference (§6.2.4), or unboxing (§6.2.5) conversion exists // SPEC: from E to T. // SPEC: • The type of E or T is an open type. // SPEC: • E is the null literal. // SPEC VIOLATION: The specification contains an error in the list of legal conversions above. // SPEC VIOLATION: If we have "class C<T, U> where T : U where U : class" then there is // SPEC VIOLATION: an implicit conversion from T to U, but it is not an identity, reference or // SPEC VIOLATION: boxing conversion. It will be one of those at runtime, but at compile time // SPEC VIOLATION: we do not know which, and therefore cannot classify it as any of those. // SPEC VIOLATION: See Microsoft.CodeAnalysis.CSharp.UnitTests.SyntaxBinderTests.TestAsOperator_SpecErrorCase() test for an example. // SPEC VIOLATION: The specification also unintentionally allows the case where requirement 2 above: // SPEC VIOLATION: "The type of E or T is an open type" is true, but type of E is void type, i.e. T is an open type. // SPEC VIOLATION: Dev10 compiler correctly generates an error for this case and we will maintain compatibility. bool hasErrors = false; switch (conversionKind) { case ConversionKind.ImplicitReference: case ConversionKind.Boxing: case ConversionKind.ImplicitNullable: case ConversionKind.Identity: case ConversionKind.ExplicitNullable: case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: break; default: // Generate an error if there is no possible legal conversion and both the operandType // and the targetType are closed types OR operandType is void type, otherwise we need a runtime check if (!operandType.ContainsTypeParameter() && !targetType.ContainsTypeParameter() || operandType.SpecialType == SpecialType.System_Void) { SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, operandType, targetType); Error(diagnostics, ErrorCode.ERR_NoExplicitBuiltinConv, node, distinguisher.First, distinguisher.Second); hasErrors = true; } break; } if (!hasErrors) { ReportAsOperatorConstantWarnings(node, diagnostics, operandType, targetType, conversionKind, operandConstantValue); } return hasErrors; }
internal void GenerateAnonymousFunctionConversionError(DiagnosticBag diagnostics, CSharpSyntaxNode syntax, UnboundLambda anonymousFunction, TypeSymbol targetType) { Debug.Assert((object)targetType != null); Debug.Assert(anonymousFunction != null); // Is the target type simply bad? // If the target type is an error then we've already reported a diagnostic. Don't bother // reporting the conversion error. if (targetType.IsErrorType() || syntax.HasErrors) { return; } // CONSIDER: Instead of computing this again, cache the reason why the conversion failed in // CONSIDER: the Conversion result, and simply report that. var reason = Conversions.IsAnonymousFunctionCompatibleWithType(anonymousFunction, targetType); // It is possible that the conversion from lambda to delegate is just fine, and // that we ended up here because the target type, though itself is not an error // type, contains a type argument which is an error type. For example, converting // (Foo foo)=>{} to Action<Foo> is a perfectly legal conversion even if Foo is undefined! // In that case we have already reported an error that Foo is undefined, so just bail out. if (reason == LambdaConversionResult.Success) { return; } var id = anonymousFunction.MessageID.Localize(); if (reason == LambdaConversionResult.BadTargetType) { if (ReportDelegateInvokeUseSiteDiagnostic(diagnostics, targetType, node: syntax)) { return; } // Cannot convert {0} to type '{1}' because it is not a delegate type Error(diagnostics, ErrorCode.ERR_AnonMethToNonDel, syntax, id, targetType); return; } if (reason == LambdaConversionResult.ExpressionTreeMustHaveDelegateTypeArgument) { Debug.Assert(targetType.IsExpressionTree()); Error(diagnostics, ErrorCode.ERR_ExpressionTreeMustHaveDelegate, syntax, ((NamedTypeSymbol)targetType).TypeArgumentsNoUseSiteDiagnostics[0]); return; } if (reason == LambdaConversionResult.ExpressionTreeFromAnonymousMethod) { Debug.Assert(targetType.IsExpressionTree()); Error(diagnostics, ErrorCode.ERR_AnonymousMethodToExpressionTree, syntax); return; } // At this point we know that we have either a delegate type or an expression type for the target. var delegateType = targetType.GetDelegateType(); // The target type is a vaid delegate or expression tree type. Is there something wrong with the // parameter list? // First off, is there a parameter list at all? if (reason == LambdaConversionResult.MissingSignatureWithOutParameter) { // COMPATIBILITY: The C# 4 compiler produces two errors for: // // delegate void D (out int x); // ... // D d = delegate {}; // // error CS1676: Parameter 1 must be declared with the 'out' keyword // error CS1688: Cannot convert anonymous method block without a parameter list // to delegate type 'D' because it has one or more out parameters // // This seems redundant, (because there is no "parameter 1" in the source code) // and unnecessary. I propose that we eliminate the first error. Error(diagnostics, ErrorCode.ERR_CantConvAnonMethNoParams, syntax, targetType); return; } // There is a parameter list. Does it have the right number of elements? if (reason == LambdaConversionResult.BadParameterCount) { // Delegate '{0}' does not take {1} arguments Error(diagnostics, ErrorCode.ERR_BadDelArgCount, syntax, targetType, anonymousFunction.ParameterCount); return; } // The parameter list exists and had the right number of parameters. Were any of its types bad? // If any parameter type of the lambda is an error type then suppress // further errors. We've already reported errors on the bad type. if (anonymousFunction.HasExplicitlyTypedParameterList) { for (int i = 0; i < anonymousFunction.ParameterCount; ++i) { if (anonymousFunction.ParameterType(i).IsErrorType()) { return; } } } // The parameter list exists and had the right number of parameters. Were any of its types // mismatched with the delegate parameter types? // The simplest possible case is (x, y, z)=>whatever where the target type has a ref or out parameter. var delegateParameters = delegateType.DelegateParameters(); if (reason == LambdaConversionResult.RefInImplicitlyTypedLambda) { for (int i = 0; i < anonymousFunction.ParameterCount; ++i) { var delegateRefKind = delegateParameters[i].RefKind; if (delegateRefKind != RefKind.None) { // Parameter {0} must be declared with the '{1}' keyword Error(diagnostics, ErrorCode.ERR_BadParamRef, anonymousFunction.ParameterLocation(i), i + 1, delegateRefKind.ToDisplayString()); } } return; } // See the comments in IsAnonymousFunctionCompatibleWithDelegate for an explanation of this one. if (reason == LambdaConversionResult.StaticTypeInImplicitlyTypedLambda) { for (int i = 0; i < anonymousFunction.ParameterCount; ++i) { if (delegateParameters[i].Type.IsStatic) { // {0}: Static types cannot be used as parameter Error(diagnostics, ErrorCode.ERR_ParameterIsStaticClass, anonymousFunction.ParameterLocation(i), delegateParameters[i].Type); } } return; } // Otherwise, there might be a more complex reason why the parameter types are mismatched. if (reason == LambdaConversionResult.MismatchedParameterType) { // Cannot convert {0} to delegate type '{1}' because the parameter types do not match the delegate parameter types Error(diagnostics, ErrorCode.ERR_CantConvAnonMethParams, syntax, id, targetType); Debug.Assert(anonymousFunction.ParameterCount == delegateParameters.Length); for (int i = 0; i < anonymousFunction.ParameterCount; ++i) { var lambdaParameterType = anonymousFunction.ParameterType(i); if (lambdaParameterType.IsErrorType()) { continue; } var lambdaParameterLocation = anonymousFunction.ParameterLocation(i); var lambdaRefKind = anonymousFunction.RefKind(i); var delegateParameterType = delegateParameters[i].Type; var delegateRefKind = delegateParameters[i].RefKind; if (!lambdaParameterType.Equals(delegateParameterType, ignoreCustomModifiers: true, ignoreDynamic: true)) { SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, lambdaParameterType, delegateParameterType); // Parameter {0} is declared as type '{1}{2}' but should be '{3}{4}' Error(diagnostics, ErrorCode.ERR_BadParamType, lambdaParameterLocation, i + 1, lambdaRefKind.ToPrefix(), distinguisher.First, delegateRefKind.ToPrefix(), distinguisher.Second); } else if (lambdaRefKind != delegateRefKind) { if (delegateRefKind == RefKind.None) { // Parameter {0} should not be declared with the '{1}' keyword Error(diagnostics, ErrorCode.ERR_BadParamExtraRef, lambdaParameterLocation, i + 1, lambdaRefKind.ToDisplayString()); } else { // Parameter {0} must be declared with the '{1}' keyword Error(diagnostics, ErrorCode.ERR_BadParamRef, lambdaParameterLocation, i + 1, delegateRefKind.ToDisplayString()); } } } return; } if (reason == LambdaConversionResult.BindingFailed) { var bindingResult = anonymousFunction.Bind(delegateType); Debug.Assert(ErrorFacts.PreventsSuccessfulDelegateConversion(bindingResult.Diagnostics)); diagnostics.AddRange(bindingResult.Diagnostics); return; } // UNDONE: LambdaConversionResult.VoidExpressionLambdaMustBeStatementExpression: Debug.Assert(false, "Missing case in lambda conversion error reporting"); }
protected static void GenerateImplicitConversionError(DiagnosticBag diagnostics, Compilation compilation, CSharpSyntaxNode syntax, Conversion conversion, TypeSymbol sourceType, TypeSymbol targetType, ConstantValue sourceConstantValueOpt = null) { Debug.Assert(!conversion.IsImplicit || !conversion.IsValid); // If the either type is an error then an error has already been reported // for some aspect of the analysis of this expression. (For example, something like // "garbage g = null; short s = g;" -- we don't want to report that g is not // convertible to short because we've already reported that g does not have a good type. if (!sourceType.IsErrorType() && !targetType.IsErrorType()) { if (conversion.IsExplicit) { if (sourceType.SpecialType == SpecialType.System_Double && syntax.Kind() == SyntaxKind.NumericLiteralExpression && (targetType.SpecialType == SpecialType.System_Single || targetType.SpecialType == SpecialType.System_Decimal)) { Error(diagnostics, ErrorCode.ERR_LiteralDoubleCast, syntax, (targetType.SpecialType == SpecialType.System_Single) ? "F" : "M", targetType); } else if (conversion.Kind == ConversionKind.ExplicitNumeric && sourceConstantValueOpt != null && sourceConstantValueOpt != ConstantValue.Bad && ConversionsBase.HasImplicitConstantExpressionConversion(new BoundLiteral(syntax, ConstantValue.Bad, sourceType), targetType)) { // CLEVERNESS: By passing ConstantValue.Bad, we tell HasImplicitConstantExpressionConversion to ignore the constant // value and only consider the types. // If there would be an implicit constant conversion for a different constant of the same type // (i.e. one that's not out of range), then it's more helpful to report the range check failure // than to suggest inserting a cast. Error(diagnostics, ErrorCode.ERR_ConstOutOfRange, syntax, sourceConstantValueOpt.Value, targetType); } else { SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, sourceType, targetType); Error(diagnostics, ErrorCode.ERR_NoImplicitConvCast, syntax, distinguisher.First, distinguisher.Second); } } else if (conversion.ResultKind == LookupResultKind.OverloadResolutionFailure) { Debug.Assert(conversion.IsUserDefined); ImmutableArray<MethodSymbol> originalUserDefinedConversions = conversion.OriginalUserDefinedConversions; if (originalUserDefinedConversions.Length > 1) { Error(diagnostics, ErrorCode.ERR_AmbigUDConv, syntax, originalUserDefinedConversions[0], originalUserDefinedConversions[1], sourceType, targetType); } else { Debug.Assert(originalUserDefinedConversions.Length == 0, "How can there be exactly one applicable user-defined conversion if the conversion doesn't exist?"); SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, sourceType, targetType); Error(diagnostics, ErrorCode.ERR_NoImplicitConv, syntax, distinguisher.First, distinguisher.Second); } } else { SymbolDistinguisher distinguisher = new SymbolDistinguisher(compilation, sourceType, targetType); Error(diagnostics, ErrorCode.ERR_NoImplicitConv, syntax, distinguisher.First, distinguisher.Second); } } }
public Description(SymbolDistinguisher distinguisher, int index) { _distinguisher = distinguisher; _index = index; }
private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, Binder originalBinder) { // Use the right binder to avoid seeing iteration variable BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindValue(_syntax.Expression, diagnostics, BindValueKind.RValue); ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); TypeSymbol inferredType; bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); // These should only occur when special types are missing or malformed. hasErrors = hasErrors || (object)builder.GetEnumeratorMethod == null || (object)builder.MoveNextMethod == null || (object)builder.CurrentPropertyGetter == null; // Check for local variable conflicts in the *enclosing* binder; obviously the *current* // binder has a local that matches! var 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 = _syntax.Type; bool isVar; AliasSymbol alias; TypeSymbol declType = BindType(typeSyntax, diagnostics, out isVar, out alias); TypeSymbol iterationVariableType; if (isVar) { iterationVariableType = inferredType ?? CreateErrorType("var"); } else { Debug.Assert((object)declType != null); iterationVariableType = declType; } BoundTypeExpression boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); this.IterationVariable.SetTypeSymbol(iterationVariableType); BoundStatement body = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics); hasErrors = hasErrors || iterationVariableType.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, null, // can't be sure that it's complete default(Conversion), boundIterationVariableType, this.IterationVariable, collectionExpr, body, CheckOverflowAtRuntime, this.BreakLabel, this.ContinueLabel, hasErrors); } hasErrors |= hasNameConflicts; var foreachKeyword = _syntax.ForEachKeyword; ReportDiagnosticsIfObsolete(diagnostics, builder.GetEnumeratorMethod, foreachKeyword, hasBaseReceiver: false); ReportDiagnosticsIfObsolete(diagnostics, builder.MoveNextMethod, 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). HashSet<DiagnosticInfo> useSiteDiagnostics = null; Conversion elementConversion = this.Conversions.ClassifyConversionForCast(inferredType, iterationVariableType, ref useSiteDiagnostics); if (!elementConversion.IsValid) { ImmutableArray<MethodSymbol> originalUserDefinedConversions = elementConversion.OriginalUserDefinedConversions; if (originalUserDefinedConversions.Length > 1) { diagnostics.Add(ErrorCode.ERR_AmbigUDConv, _syntax.ForEachKeyword.GetLocation(), originalUserDefinedConversions[0], originalUserDefinedConversions[1], inferredType, iterationVariableType); } else { SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType, iterationVariableType); diagnostics.Add(ErrorCode.ERR_NoExplicitConv, _syntax.ForEachKeyword.GetLocation(), distinguisher.First, distinguisher.Second); } hasErrors = true; } else { ReportDiagnosticsIfObsolete(diagnostics, elementConversion, _syntax.ForEachKeyword, hasBaseReceiver: false); } // 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). builder.CollectionConversion = this.Conversions.ClassifyConversionFromExpression(collectionExpr, builder.CollectionType, ref useSiteDiagnostics); builder.CurrentConversion = this.Conversions.ClassifyConversion(builder.CurrentPropertyGetter.ReturnType, builder.ElementType, ref useSiteDiagnostics); builder.EnumeratorConversion = this.Conversions.ClassifyConversion(builder.GetEnumeratorMethod.ReturnType, GetSpecialType(SpecialType.System_Object, diagnostics, _syntax), ref useSiteDiagnostics); diagnostics.Add(_syntax.ForEachKeyword.GetLocation(), useSiteDiagnostics); // 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(builder.CollectionConversion.IsValid); Debug.Assert(builder.CurrentConversion.IsValid || (builder.ElementType.IsPointerType() && collectionExpr.Type.IsArray()) || (builder.ElementType.IsNullableType() && builder.ElementType.GetMemberTypeArgumentsNoUseSiteDiagnostics().Single().IsErrorType() && collectionExpr.Type.IsArray())); Debug.Assert(builder.EnumeratorConversion.IsValid || this.Compilation.GetSpecialType(SpecialType.System_Object).TypeKind == TypeKind.Error || !useSiteDiagnostics.IsNullOrEmpty(), "Conversions to object succeed unless there's a problem with the object type or the source type"); // If user-defined conversions could occur here, we would need to check for ObsoleteAttribute. Debug.Assert((object)builder.CollectionConversion.Method == null, "Conversion from collection expression to collection type should not be user-defined"); Debug.Assert((object)builder.CurrentConversion.Method == null, "Conversion from Current property type to element type should not be user-defined"); Debug.Assert((object)builder.EnumeratorConversion.Method == null, "Conversion from GetEnumerator return type to System.Object should not be user-defined"); // 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. BoundConversion convertedCollectionExpression = new BoundConversion( collectionExpr.Syntax, collectionExpr, builder.CollectionConversion, CheckOverflowAtRuntime, false, ConstantValue.NotAvailable, builder.CollectionType); return new BoundForEachStatement( _syntax, builder.Build(this.Flags), elementConversion, boundIterationVariableType, this.IterationVariable, convertedCollectionExpression, body, CheckOverflowAtRuntime, this.BreakLabel, this.ContinueLabel, hasErrors); }
public Description(SymbolDistinguisher distinguisher, int index) { this.distinguisher = distinguisher; this.index = index; }