diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs b/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs index ff791637f4f980027187f88daa5d6a62139e6811..acc1399276ab1efd7b95394cb828dd0a7fe06f3d 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs @@ -16,7 +16,7 @@ internal sealed class ForEachEnumeratorInfo // Types identified by the algorithm in the spec (8.8.4). public readonly TypeSymbol CollectionType; // public readonly TypeSymbol EnumeratorType; // redundant - return type of GetEnumeratorMethod - public readonly TypeSymbol ElementType; + public readonly TypeSymbolWithAnnotations ElementType; // Members required by the "pattern" based approach. Also populated for other approaches. public readonly MethodSymbol GetEnumeratorMethod; @@ -37,7 +37,7 @@ internal sealed class ForEachEnumeratorInfo private ForEachEnumeratorInfo( TypeSymbol collectionType, - TypeSymbol elementType, + TypeSymbolWithAnnotations elementType, MethodSymbol getEnumeratorMethod, MethodSymbol currentPropertyGetter, MethodSymbol moveNextMethod, @@ -69,7 +69,7 @@ internal sealed class ForEachEnumeratorInfo internal struct Builder { public TypeSymbol CollectionType; - public TypeSymbol ElementType; + public TypeSymbolWithAnnotations ElementType; public MethodSymbol GetEnumeratorMethod; public MethodSymbol CurrentPropertyGetter; diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 08363e63db9def98126b977bd125df4e1d2fb405..9cf1a3cea605ded2cc8d0aa9d672dbd367f9d68f 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -164,13 +164,13 @@ internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnos BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindValue(_syntax.Expression, diagnostics, BindValueKind.RValue); ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); - TypeSymbol inferredType; + TypeSymbolWithAnnotations inferredType; bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); 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, this.LocalScopeDepth, inferredType ?? CreateErrorType("var")); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, this.LocalScopeDepth, inferredType?.TypeSymbol ?? CreateErrorType("var")); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; @@ -192,7 +192,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindValue(_syntax.Expression, diagnostics, BindValueKind.RValue); ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); - TypeSymbol inferredType; + TypeSymbolWithAnnotations inferredType; bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); // These should only occur when special types are missing or malformed. @@ -226,18 +226,18 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, if (isVar) { - iterationVariableType = inferredType ?? CreateErrorType("var"); + declType = inferredType ?? TypeSymbolWithAnnotations.Create(CreateErrorType("var"), isNullableIfReferenceType: null); } else { Debug.Assert((object)declType != null); - iterationVariableType = declType.TypeSymbol; } + iterationVariableType = declType.TypeSymbol; boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); SourceLocalSymbol local = this.IterationVariable; - local.SetTypeSymbol(TypeSymbolWithAnnotations.Create(iterationVariableType)); + local.SetTypeSymbol(declType); local.SetValEscape(collectionEscape); if (local.RefKind != RefKind.None) @@ -280,7 +280,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, case SyntaxKind.ForEachVariableStatement: { var node = (ForEachVariableStatementSyntax)_syntax; - iterationVariableType = inferredType ?? CreateErrorType("var"); + iterationVariableType = inferredType?.TypeSymbol ?? CreateErrorType("var"); var variables = node.Variable; if (variables.IsDeconstructionLeft()) @@ -370,18 +370,18 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, // but it turns out that these are equivalent (when both are available). HashSet useSiteDiagnostics = null; - Conversion elementConversion = this.Conversions.ClassifyConversionFromType(inferredType, iterationVariableType, ref useSiteDiagnostics, forCast: true); + Conversion elementConversion = this.Conversions.ClassifyConversionFromType(inferredType.TypeSymbol, iterationVariableType, ref useSiteDiagnostics, forCast: true); if (!elementConversion.IsValid) { ImmutableArray originalUserDefinedConversions = elementConversion.OriginalUserDefinedConversions; if (originalUserDefinedConversions.Length > 1) { - diagnostics.Add(ErrorCode.ERR_AmbigUDConv, foreachKeyword.GetLocation(), originalUserDefinedConversions[0], originalUserDefinedConversions[1], inferredType, iterationVariableType); + diagnostics.Add(ErrorCode.ERR_AmbigUDConv, foreachKeyword.GetLocation(), originalUserDefinedConversions[0], originalUserDefinedConversions[1], inferredType.TypeSymbol, iterationVariableType); } else { - SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType, iterationVariableType); + SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType.TypeSymbol, iterationVariableType); diagnostics.Add(ErrorCode.ERR_NoExplicitConv, foreachKeyword.GetLocation(), distinguisher.First, distinguisher.Second); } hasErrors = true; @@ -395,7 +395,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, // 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.ClassifyConversionFromType(builder.CurrentPropertyGetter.ReturnType.TypeSymbol, builder.ElementType, ref useSiteDiagnostics); + builder.CurrentConversion = this.Conversions.ClassifyConversionFromType(builder.CurrentPropertyGetter.ReturnType.TypeSymbol, builder.ElementType.TypeSymbol, ref useSiteDiagnostics); var getEnumeratorType = builder.GetEnumeratorMethod.ReturnType.TypeSymbol; // we never convert struct enumerators to object - it is done only for null-checks. @@ -419,7 +419,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, 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())); + (builder.ElementType.IsNullableType() && builder.ElementType.TypeSymbol.GetMemberTypeArgumentsNoUseSiteDiagnostics().Single().IsErrorType() && collectionExpr.Type.IsArray())); Debug.Assert(builder.EnumeratorConversion.IsValid || this.Compilation.GetSpecialType(SpecialType.System_Object).TypeKind == TypeKind.Error || !useSiteDiagnostics.IsNullOrEmpty(), @@ -460,18 +460,18 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, hasErrors); } - internal TypeSymbol InferCollectionElementType(DiagnosticBag diagnostics, ExpressionSyntax collectionSyntax) + internal TypeSymbolWithAnnotations InferCollectionElementType(DiagnosticBag diagnostics, ExpressionSyntax collectionSyntax) { // Use the right binder to avoid seeing iteration variable BoundExpression collectionExpr = this.GetBinder(collectionSyntax).BindValue(collectionSyntax, diagnostics, BindValueKind.RValue); ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); - TypeSymbol inferredType; + TypeSymbolWithAnnotations inferredType; GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); return inferredType; } - private bool GetEnumeratorInfoAndInferCollectionElementType(ref ForEachEnumeratorInfo.Builder builder, ref BoundExpression collectionExpr, DiagnosticBag diagnostics, out TypeSymbol inferredType) + private bool GetEnumeratorInfoAndInferCollectionElementType(ref ForEachEnumeratorInfo.Builder builder, ref BoundExpression collectionExpr, DiagnosticBag diagnostics, out TypeSymbolWithAnnotations inferredType) { UnwrapCollectionExpressionIfNullable(ref collectionExpr, diagnostics); @@ -484,7 +484,7 @@ private bool GetEnumeratorInfoAndInferCollectionElementType(ref ForEachEnumerato else if (collectionExpr.HasDynamicType()) { // If the enumerator is dynamic, it yields dynamic values - inferredType = DynamicTypeSymbol.Instance; + inferredType = TypeSymbolWithAnnotations.Create(DynamicTypeSymbol.Instance, isNullableIfReferenceType: null); } else if (collectionExpr.Type.SpecialType == SpecialType.System_String && builder.CollectionType.SpecialType == SpecialType.System_Collections_IEnumerable) { @@ -492,7 +492,7 @@ private bool GetEnumeratorInfoAndInferCollectionElementType(ref ForEachEnumerato // 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. - inferredType = GetSpecialType(SpecialType.System_Char, diagnostics, collectionExpr.Syntax); + inferredType = TypeSymbolWithAnnotations.Create(GetSpecialType(SpecialType.System_Char, diagnostics, collectionExpr.Syntax), isNullableIfReferenceType: null); } else { @@ -610,7 +610,7 @@ private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, BoundE if (SatisfiesForEachPattern(ref builder, diagnostics)) { - builder.ElementType = GetTypeOrReturnTypeWithAdjustedNullableAnnotations((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol).TypeSymbol; + builder.ElementType = GetTypeOrReturnTypeWithAdjustedNullableAnnotations((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol); // 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. @@ -660,7 +660,7 @@ private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, BoundE { // 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().TypeSymbol; + builder.ElementType = collectionType.TypeArgumentsNoUseSiteDiagnostics.Single(); MethodSymbol getEnumeratorMethod = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Collections_Generic_IEnumerable_T__GetEnumerator, diagnostics, errorLocationSyntax); if ((object)getEnumeratorMethod != null) @@ -682,11 +682,13 @@ private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, BoundE { // 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); + builder.ElementType = TypeSymbolWithAnnotations.Create( + GetSpecialType(SpecialType.System_Object, diagnostics, errorLocationSyntax), + isNullableIfReferenceType: builder.CurrentPropertyGetter?.ReturnType.IsNullable); Debug.Assert((object)builder.GetEnumeratorMethod == null || builder.GetEnumeratorMethod.ReturnType.SpecialType == SpecialType.System_Collections_IEnumerator); @@ -722,15 +724,17 @@ private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumerator if (collectionExprType.IsDynamic()) { - builder.ElementType = ((_syntax as ForEachStatementSyntax)?.Type.IsVar == true) ? - (TypeSymbol)DynamicTypeSymbol.Instance : - GetSpecialType(SpecialType.System_Object, diagnostics, _syntax); + builder.ElementType = TypeSymbolWithAnnotations.Create( + ((_syntax as ForEachStatementSyntax)?.Type.IsVar == true) ? + (TypeSymbol)DynamicTypeSymbol.Instance : + GetSpecialType(SpecialType.System_Object, diagnostics, _syntax), + isNullableIfReferenceType: null); } else { builder.ElementType = collectionExprType.SpecialType == SpecialType.System_String ? - GetSpecialType(SpecialType.System_Char, diagnostics, _syntax) : - ((ArrayTypeSymbol)collectionExprType).ElementType.TypeSymbol; + TypeSymbolWithAnnotations.Create(GetSpecialType(SpecialType.System_Char, diagnostics, _syntax), isNullableIfReferenceType: null) : + ((ArrayTypeSymbol)collectionExprType).ElementType; } // CONSIDER: diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index 6c6c13c48ed950eebe99166897e59e23954c7042..fb3f016bd1078220a6f3dd11e8090edcf1bb9953 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -840,7 +840,7 @@ public override ForEachStatementInfo GetForEachStatementInfo(CommonForEachStatem enumeratorInfoOpt.MoveNextMethod, (PropertySymbol)enumeratorInfoOpt.CurrentPropertyGetter.AssociatedSymbol, enumeratorInfoOpt.NeedsDisposeMethod ? (MethodSymbol)Compilation.GetSpecialTypeMember(SpecialMember.System_IDisposable__Dispose) : null, - enumeratorInfoOpt.ElementType, + enumeratorInfoOpt.ElementType.TypeSymbol, boundForEach.ElementConversion, enumeratorInfoOpt.CurrentConversion); } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 6aa3980f3868f8cf63ca096900c440f11ef86ba6..a80f9a2443e3a7b4fa0b0089763e95c2a8e79bc5 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -2749,7 +2749,19 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node) // declare and assign all iteration variables foreach (var iterationVariable in node.IterationVariables) { - // PROTOTYPE(NullableReferenceTypes): Mark as assigned. + int slot = GetOrCreateSlot(iterationVariable); + TypeSymbolWithAnnotations sourceType = node.EnumeratorInfoOpt?.ElementType; + bool? isNullableIfReferenceType = null; + if ((object)sourceType != null) + { + TypeSymbolWithAnnotations destinationType = iterationVariable.Type; + HashSet useSiteDiagnostics = null; + Conversion conversion = _conversions.ClassifyImplicitConversionFromType(sourceType.TypeSymbol, destinationType.TypeSymbol, ref useSiteDiagnostics); + // PROTOTYPE(NullableReferenceTypes): Report nullability mismatch if any. + TypeSymbolWithAnnotations result = InferResultNullability(operand: null, conversion, destinationType.TypeSymbol, sourceType); + isNullableIfReferenceType = result.IsNullable; + } + this.State[slot] = !isNullableIfReferenceType; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs index 7e5ed14fc9e0f3feb30a3580d3b0169b426d49a1..410cc5d28c6b787ab8fb57c43d9fb0a57bf3dad9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs @@ -106,7 +106,7 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n BoundStatement rewrittenBody = (BoundStatement)Visit(node.Body); TypeSymbol enumeratorType = enumeratorInfo.GetEnumeratorMethod.ReturnType.TypeSymbol; - TypeSymbol elementType = enumeratorInfo.ElementType; + TypeSymbol elementType = enumeratorInfo.ElementType.TypeSymbol; // E e LocalSymbol enumeratorVar = _factory.SynthesizedLocal(enumeratorType, syntax: forEachSyntax, kind: SynthesizedLocalKind.ForEachEnumerator); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index f7d7802d0f9edf2b48da81e5f430a4d324631fbe..07ca9077a7ea2314a569d07877a7b968269ab4ab 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -623,8 +623,7 @@ private sealed class ForEachLocalSymbol : SourceLocalSymbol protected override TypeSymbolWithAnnotations InferTypeOfVarVariable(DiagnosticBag diagnostics) { - var type = ForEachLoopBinder.InferCollectionElementType(diagnostics, _collection); - return type == null ? null : TypeSymbolWithAnnotations.Create(type); + return ForEachLoopBinder.InferCollectionElementType(diagnostics, _collection); } /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs index 5c8551fd7ef8e352354f92d54cdcc3394298c835..651f79a8ecf462af322e4363ba70757533a70a30 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs @@ -3049,7 +3049,7 @@ private static BoundForEachStatement GetBoundForEachStatement(string text, param Assert.Null(statementInfo.DisposeMethod); } - Assert.Equal(enumeratorInfo.ElementType, statementInfo.ElementType); + Assert.Equal(enumeratorInfo.ElementType.TypeSymbol, statementInfo.ElementType); Assert.Equal(boundNode.ElementConversion, statementInfo.ElementConversion); Assert.Equal(enumeratorInfo.CurrentConversion, statementInfo.CurrentConversion); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index bdd048f22352c8e0d10e073db783d68ffdd0697e..b40fe57aa102fdd658c01ae9550dabfcf0402aa5 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -19516,6 +19516,275 @@ public void ExplicitCast_StaticType() Diagnostic(ErrorCode.ERR_ConvertToStaticClass, "(C?)y").WithArguments("C").WithLocation(4, 36)); } + [Fact] + public void ForEach_01() + { + var source = +@"class Enumerable +{ + public Enumerator GetEnumerator() => new Enumerator(); +} +class Enumerator +{ + public object Current => throw null; + public bool MoveNext() => false; +} +class C +{ + static void F(Enumerable e) + { + foreach (var x in e) + x.ToString(); + foreach (string y in e) + y.ToString(); + foreach (string? z in e) + z.ToString(); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEach_02() + { + var source = +@"class Enumerable +{ + public Enumerator GetEnumerator() => new Enumerator(); +} +class Enumerator +{ + public object? Current => throw null; + public bool MoveNext() => false; +} +class C +{ + static void F(Enumerable e) + { + foreach (var x in e) + x.ToString(); + foreach (object y in e) + y.ToString(); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics( + // (15,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 13), + // (17,13): warning CS8602: Possible dereference of a null reference. + // y.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(17, 13)); + } + + [Fact] + public void ForEach_03() + { + var source = +@"using System.Collections; +namespace System +{ + public class Object + { + public string ToString() => throw null; + } + public abstract class ValueType { } + public struct Void { } + public struct Boolean { } + public class String { } +} +namespace System.Collections +{ + public interface IEnumerable + { + IEnumerator GetEnumerator(); + } + public interface IEnumerator + { + object? Current { get; } + bool MoveNext(); + } +} +class Enumerable : IEnumerable +{ + IEnumerator IEnumerable.GetEnumerator() => throw null; +} +class C +{ + static void F(Enumerable e) + { + foreach (var x in e) + x.ToString(); + foreach (object y in e) + y.ToString(); + } + static void G(IEnumerable e) + { + foreach (var z in e) + z.ToString(); + foreach (object w in e) + w.ToString(); + } +}"; + var comp = CreateEmptyCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics( + // (34,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(34, 13), + // (36,13): warning CS8602: Possible dereference of a null reference. + // y.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(36, 13), + // (41,13): warning CS8602: Possible dereference of a null reference. + // z.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "z").WithLocation(41, 13), + // (43,13): warning CS8602: Possible dereference of a null reference. + // w.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "w").WithLocation(43, 13)); + } + + // z.ToString() should warn if IEnumerator.Current is annotated as `object?`. + [Fact] + public void ForEach_04() + { + var source = +@"using System.Collections; +using System.Collections.Generic; +class C +{ + static void F(IEnumerable cx, object?[] cy) + { + foreach (var x in cx) + x.ToString(); + foreach (object? y in cy) + y.ToString(); + foreach (object? z in (IEnumerable)cx) + z.ToString(); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13), + // (10,13): warning CS8602: Possible dereference of a null reference. + // y.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(10, 13)); + } + + [Fact] + public void ForEach_05() + { + var source = +@"class C +{ + static void F1(dynamic c) + { + foreach (var x in c) + x.ToString(); + foreach (object? y in c) + y.ToString(); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEach_06() + { + var source = +@"using System.Collections; +using System.Collections.Generic; +class C : IEnumerable where T : class +{ + IEnumerator IEnumerable.GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; +} +class P +{ + static void F(C c) where T : class + { + foreach (var x in c) + x.ToString(); + foreach (T? y in c) + y.ToString(); + foreach (T z in c) + z.ToString(); + foreach (object w in c) + w.ToString(); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics( + // (13,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 13), + // (15,13): warning CS8602: Possible dereference of a null reference. + // y.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(15, 13), + // (17,13): warning CS8602: Possible dereference of a null reference. + // z.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "z").WithLocation(17, 13), + // (19,13): warning CS8602: Possible dereference of a null reference. + // w.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "w").WithLocation(19, 13)); + } + + [Fact] + public void ForEach_07() + { + var source = +@"struct S where T : class +{ + public E GetEnumerator() => new E(); +} +struct E where T : class +{ + public T Current => throw null; + public bool MoveNext() => false; +} +class P +{ + static void F1() where T : class + { + foreach (var x1 in new S()) + x1.ToString(); + foreach (T y1 in new S()) + y1.ToString(); + foreach (T? z1 in new S()) + z1.ToString(); + foreach (object? w1 in new S()) + w1.ToString(); + } + static void F2() where T : class + { + foreach (var x2 in new S()) + x2.ToString(); + foreach (T y2 in new S()) + y2.ToString(); + foreach (T? z2 in new S()) + z2.ToString(); + foreach (object? w2 in new S()) + w2.ToString(); + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics( + // (26,13): warning CS8602: Possible dereference of a null reference. + // x2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(26, 13), + // (28,13): warning CS8602: Possible dereference of a null reference. + // y2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y2").WithLocation(28, 13), + // (30,13): warning CS8602: Possible dereference of a null reference. + // z2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "z2").WithLocation(30, 13), + // (32,13): warning CS8602: Possible dereference of a null reference. + // w2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "w2").WithLocation(32, 13)); + } + [Fact] public void UnconstrainedTypeParameter_MayBeNonNullable() {