From 2a705647f2e00352558732eef06367b2bd8358a5 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Thu, 11 Feb 2021 00:17:30 +0330 Subject: [PATCH] WIP --- .../Portable/Binder/Binder_Expressions.cs | 204 ++-- .../CSharp/Portable/Binder/Binder_ForEach.cs | 978 ++++++++++++++++++ .../CSharp/Portable/Binder/Binder_Patterns.cs | 287 ++++- .../Portable/Binder/DecisionDagBuilder.cs | 367 ++++++- .../Portable/Binder/ForEachLoopBinder.cs | 965 +---------------- .../Portable/BoundTree/BoundDagEvaluation.cs | 88 +- .../CSharp/Portable/BoundTree/BoundDagTest.cs | 10 +- .../CSharp/Portable/BoundTree/BoundNodes.xml | 58 +- .../CSharp/Portable/Errors/ErrorCode.cs | 4 + .../CSharp/Portable/Errors/MessageID.cs | 11 + .../FlowAnalysis/DefiniteAssignment.cs | 19 +- .../FlowAnalysis/NullableWalker_Patterns.cs | 77 +- .../Generated/BoundNodes.xml.Generated.cs | 719 ++++++++++++- .../Portable/Generated/CSharp.Generated.g4 | 11 +- .../Syntax.xml.Internal.Generated.cs | 386 ++++++- .../Syntax.xml.Main.Generated.cs | 50 +- .../Syntax.xml.Syntax.Generated.cs | 119 ++- .../LocalRewriter.DecisionDagRewriter.cs | 152 ++- .../LocalRewriter.PatternLocalRewriter.cs | 138 ++- .../LocalRewriter_ForEachStatement.cs | 14 +- .../LocalRewriter_IsPatternOperator.cs | 8 + .../Lowering/SyntheticBoundNodeFactory.cs | 5 + .../Microsoft.CodeAnalysis.CSharp.csproj | 4 +- .../Parser/LanguageParser_Patterns.cs | 38 +- .../CSharp/Portable/Syntax/Syntax.xml | 18 + .../CSharp/Portable/Syntax/SyntaxKind.cs | 2 + .../CSharp/Portable/Utilities/IValueSet.cs | 4 + .../Utilities/ValueSetFactory.BoolValueSet.cs | 4 + .../ValueSetFactory.EnumeratedValueSet.cs | 4 + .../ValueSetFactory.FloatingValueSet.cs | 4 + .../Utilities/ValueSetFactory.NintValueSet.cs | 4 + .../ValueSetFactory.NuintValueSet.cs | 4 + .../ValueSetFactory.NumericValueSet.cs | 4 + .../Semantics/PatternMatchingTests5.cs | 586 +++++++++++ .../Symbol/Symbols/MissingSpecialMember.cs | 3 + .../Generated/Syntax.Test.xml.Generated.cs | 170 ++- .../Syntax/Parsing/PatternParsingTests.cs | 2 +- .../Syntax/Parsing/PatternParsingTests2.cs | 331 ++++++ .../Core/Portable/Diagnostic/DiagnosticBag.cs | 14 + src/Compilers/Core/Portable/SpecialMember.cs | 1 + src/Compilers/Core/Portable/SpecialMembers.cs | 9 + .../Core/Portable/WellKnownMember.cs | 4 + .../Core/Portable/WellKnownMembers.cs | 26 + src/Compilers/Core/Portable/WellKnownTypes.cs | 6 +- 44 files changed, 4726 insertions(+), 1186 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Binder/Binder_ForEach.cs create mode 100644 src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs create mode 100644 src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index cffdb285f13a9..300912d9ed0ea 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -7854,19 +7855,53 @@ private bool TryBindIndexOrRangeIndexer( var argument = arguments.Arguments[0]; var argType = argument.Type; - bool argIsIndex = TypeSymbol.Equals(argType, - Compilation.GetWellKnownType(WellKnownType.System_Index), - TypeCompareKind.ConsiderEverything); - bool argIsRange = !argIsIndex && TypeSymbol.Equals(argType, - Compilation.GetWellKnownType(WellKnownType.System_Range), - TypeCompareKind.ConsiderEverything); - - if ((!argIsIndex && !argIsRange) || + ThreeState argIsIndexNotRange = + TypeSymbol.Equals(argType, Compilation.GetWellKnownType(WellKnownType.System_Index), TypeCompareKind.ConsiderEverything) ? ThreeState.True : + TypeSymbol.Equals(argType, Compilation.GetWellKnownType(WellKnownType.System_Range), TypeCompareKind.ConsiderEverything) ? ThreeState.False : + ThreeState.Unknown; + + if (!argIsIndexNotRange.HasValue() || !(receiverOpt?.Type is TypeSymbol receiverType)) { return false; } + if (!TryFindIndexOrRangeIndexerPattern(syntax, receiverOpt, receiverType, argIsIndex: argIsIndexNotRange.Value(), + out var lengthOrCountProperty, out var patternSymbol, out var returnType, diagnostics)) + { + return false; + } + + patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( + syntax, + receiverOpt, + lengthOrCountProperty, + patternSymbol, + BindToNaturalType(argument, diagnostics), + returnType); + + _ = MessageID.IDS_FeatureIndexOperator.CheckFeatureAvailability(diagnostics, syntax); + if (arguments.Names.Count > 0) + { + diagnostics.Add( + !argIsIndexNotRange.Value() + ? ErrorCode.ERR_ImplicitRangeIndexerWithName + : ErrorCode.ERR_ImplicitIndexIndexerWithName, + arguments.Names[0].GetLocation()); + } + return true; + } + +#nullable enable + private bool TryFindIndexOrRangeIndexerPattern( + SyntaxNode syntax, + BoundExpression? receiverOpt, + TypeSymbol receiverType, bool argIsIndex, + [NotNullWhen(true)] out PropertySymbol? lengthOrCountProperty, + [NotNullWhen(true)] out Symbol? patternSymbol, + [NotNullWhen(true)] out TypeSymbol? returnType, + DiagnosticBag diagnostics) + { // SPEC: // An indexer invocation with a single argument of System.Index or System.Range will @@ -7877,25 +7912,21 @@ private bool TryBindIndexOrRangeIndexer( // 2. For Index: Has an accessible indexer with a single int parameter // For Range: Has an accessible Slice method that takes two int parameters - PropertySymbol lengthOrCountProperty; - var lookupResult = LookupResult.GetInstance(); - HashSet useSiteDiagnostics = null; + HashSet? useSiteDiagnostics = null; // Look for Length first - - if (!tryLookupLengthOrCount(WellKnownMemberNames.LengthPropertyName, out lengthOrCountProperty) && - !tryLookupLengthOrCount(WellKnownMemberNames.CountPropertyName, out lengthOrCountProperty)) + if (!TryLookupLengthOrCount(WellKnownMemberNames.LengthPropertyName, receiverType, lookupResult, out lengthOrCountProperty, ref useSiteDiagnostics) && + !TryLookupLengthOrCount(WellKnownMemberNames.CountPropertyName, receiverType, lookupResult, out lengthOrCountProperty, ref useSiteDiagnostics)) { + patternSymbol = null; + returnType = null; return false; } - Debug.Assert(lengthOrCountProperty is { }); - if (argIsIndex) { // Look for `T this[int i]` indexer - LookupMembersInType( lookupResult, receiverType, @@ -7917,43 +7948,32 @@ candidate is PropertySymbol property && property.OriginalDefinition is { ParameterCount: 1 } original && isIntNotByRef(original.Parameters[0])) { + patternSymbol = property; + returnType = property.Type; CheckImplicitThisCopyInReadOnlyMember(receiverOpt, lengthOrCountProperty.GetMethod, diagnostics); - // note: implicit copy check on the indexer accessor happens in CheckPropertyValueKind - patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( - syntax, - receiverOpt, - lengthOrCountProperty, - property, - BindToNaturalType(argument, diagnostics), - property.Type); - break; + checkWellKnown(WellKnownMember.System_Index__GetOffset); + return true; } } } } else if (receiverType.SpecialType == SpecialType.System_String) { - Debug.Assert(argIsRange); + Debug.Assert(!argIsIndex); // Look for Substring var substring = (MethodSymbol)Compilation.GetSpecialTypeMember(SpecialMember.System_String__Substring); if (substring is object) { - patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( - syntax, - receiverOpt, - lengthOrCountProperty, - substring, - BindToNaturalType(argument, diagnostics), - substring.ReturnType); - checkWellKnown(WellKnownMember.System_Range__get_Start); - checkWellKnown(WellKnownMember.System_Range__get_End); + patternSymbol = substring; + returnType = substring.ReturnType; + checkWellKnownForRange(); + return true; } } else { - Debug.Assert(argIsRange); + Debug.Assert(!argIsIndex); // Look for `T Slice(int, int)` indexer - LookupMembersInType( lookupResult, receiverType, @@ -7977,50 +7997,35 @@ method.OriginalDefinition is var original && isIntNotByRef(original.Parameters[0]) && isIntNotByRef(original.Parameters[1])) { + patternSymbol = method; + returnType = method.ReturnType; CheckImplicitThisCopyInReadOnlyMember(receiverOpt, lengthOrCountProperty.GetMethod, diagnostics); CheckImplicitThisCopyInReadOnlyMember(receiverOpt, method, diagnostics); - patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( - syntax, - receiverOpt, - lengthOrCountProperty, - method, - BindToNaturalType(argument, diagnostics), - method.ReturnType); - checkWellKnown(WellKnownMember.System_Range__get_Start); - checkWellKnown(WellKnownMember.System_Range__get_End); - break; + checkWellKnownForRange(); + return true; } } } } - cleanup(lookupResult, ref useSiteDiagnostics); - if (patternIndexerAccess is null) - { - return false; - } - - _ = MessageID.IDS_FeatureIndexOperator.CheckFeatureAvailability(diagnostics, syntax); - checkWellKnown(WellKnownMember.System_Index__GetOffset); - if (arguments.Names.Count > 0) - { - diagnostics.Add( - argIsRange - ? ErrorCode.ERR_ImplicitRangeIndexerWithName - : ErrorCode.ERR_ImplicitIndexIndexerWithName, - arguments.Names[0].GetLocation()); - } - return true; - - static void cleanup(LookupResult lookupResult, ref HashSet useSiteDiagnostics) - { - lookupResult.Free(); - useSiteDiagnostics = null; - } + lookupResult.Free(); + useSiteDiagnostics = null; + patternSymbol = null; + returnType = null; + return false; +#pragma warning disable CS8762 // Parameter may not have a null value when exiting in some condition. static bool isIntNotByRef(ParameterSymbol param) => param.Type.SpecialType == SpecialType.System_Int32 && param.RefKind == RefKind.None; +#pragma warning restore CS8762 // Parameter may not have a null value when exiting in some condition. + + void checkWellKnownForRange() + { + checkWellKnown(WellKnownMember.System_Range__get_Start); + checkWellKnown(WellKnownMember.System_Range__get_End); + checkWellKnown(WellKnownMember.System_Index__GetOffset); + } void checkWellKnown(WellKnownMember member) { @@ -8029,39 +8034,42 @@ void checkWellKnown(WellKnownMember member) // the user from getting surprising errors when optimizations fail _ = GetWellKnownTypeMember(Compilation, member, diagnostics, syntax: syntax); } + } + + private bool TryLookupLengthOrCount( + string propertyName, TypeSymbol receiverType, LookupResult lookupResult, + [NotNullWhen(true)] out PropertySymbol? valid, ref HashSet? useSiteDiagnostics) + { + LookupMembersInType( + lookupResult, + receiverType, + propertyName, + arity: 0, + basesBeingResolved: null, + LookupOptions.Default, + originalBinder: this, + diagnose: false, + useSiteDiagnostics: ref useSiteDiagnostics); - bool tryLookupLengthOrCount(string propertyName, out PropertySymbol valid) + if (lookupResult.IsSingleViable && + lookupResult.Symbols[0] is PropertySymbol property && + property.GetOwnOrInheritedGetMethod()?.OriginalDefinition is MethodSymbol getMethod && + getMethod.ReturnType.SpecialType == SpecialType.System_Int32 && + getMethod.RefKind == RefKind.None && + !getMethod.IsStatic && + IsAccessible(getMethod, ref useSiteDiagnostics)) { - LookupMembersInType( - lookupResult, - receiverType, - propertyName, - arity: 0, - basesBeingResolved: null, - LookupOptions.Default, - originalBinder: this, - diagnose: false, - useSiteDiagnostics: ref useSiteDiagnostics); - - if (lookupResult.IsSingleViable && - lookupResult.Symbols[0] is PropertySymbol property && - property.GetOwnOrInheritedGetMethod()?.OriginalDefinition is MethodSymbol getMethod && - getMethod.ReturnType.SpecialType == SpecialType.System_Int32 && - getMethod.RefKind == RefKind.None && - !getMethod.IsStatic && - IsAccessible(getMethod, ref useSiteDiagnostics)) - { - lookupResult.Clear(); - useSiteDiagnostics = null; - valid = property; - return true; - } lookupResult.Clear(); useSiteDiagnostics = null; - valid = null; - return false; + valid = property; + return true; } + lookupResult.Clear(); + useSiteDiagnostics = null; + valid = null; + return false; } +#nullable restore private ErrorPropertySymbol CreateErrorPropertySymbol(ImmutableArray propertyGroup) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_ForEach.cs b/src/Compilers/CSharp/Portable/Binder/Binder_ForEach.cs new file mode 100644 index 0000000000000..904001b6263ea --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/Binder_ForEach.cs @@ -0,0 +1,978 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class Binder + { + protected const string GetEnumeratorMethodName = WellKnownMemberNames.GetEnumeratorMethodName; + private const string CurrentPropertyName = WellKnownMemberNames.CurrentPropertyName; + private const string MoveNextMethodName = WellKnownMemberNames.MoveNextMethodName; + protected const string GetAsyncEnumeratorMethodName = WellKnownMemberNames.GetAsyncEnumeratorMethodName; + private const string MoveNextAsyncMethodName = WellKnownMemberNames.MoveNextAsyncMethodName; + + private BoundExpression UnwrapCollectionExpressionIfNullable(BoundExpression collectionExpr, DiagnosticBag 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, + method: nullableValueGetter); + } + else + { + return new BoundBadExpression( + exprSyntax, + LookupResultKind.Empty, + ImmutableArray.Empty, + ImmutableArray.Create(collectionExpr), + collectionExprType.GetNullableUnderlyingType()) + { WasCompilerGenerated = true }; // Don't affect the type in the SemanticModel. + } + } + + return collectionExpr; + } + + protected enum EnumeratorResult + { + Succeeded, + FailedNotReported, + FailedAndReported + } + + protected EnumeratorResult GetEnumeratorInfo(SyntaxNode syntax, SyntaxNode expr, ref ForEachEnumeratorInfo.Builder builder, ref BoundExpression collectionExpr, bool isAsync, DiagnosticBag diagnostics) + { + TypeSymbol collectionExprType = collectionExpr.Type; + + if (collectionExprType is null) // There's no way to enumerate something without a type. + { + if (!ReportConstantNullCollectionExpr(expr, collectionExpr, diagnostics)) + { + // Anything else with a null type is a method group or anonymous function + diagnostics.Add(ErrorCode.ERR_AnonMethGrpInForEach, expr.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, expr.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(expr, 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, ref builder, unwrappedCollectionExpr, isAsync, viaExtensionMethod: false, diagnostics, expr)) + { + collectionExpr = unwrappedCollectionExpr; + if (ReportConstantNullCollectionExpr(expr, 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, expr.Location, unwrappedCollectionExprType, 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, expr.Location, unwrappedCollectionExprType, GetAsyncEnumeratorMethodName); + return EnumeratorResult.FailedAndReported; + } + + if (SatisfiesIEnumerableInterfaces(ref builder, unwrappedCollectionExpr, isAsync, diagnostics, unwrappedCollectionExprType, expr) 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(expr, collectionExpr, diagnostics)) + { + return EnumeratorResult.FailedAndReported; + } + + builder = GetDefaultEnumeratorInfo(syntax, builder, diagnostics, collectionExprType); + return EnumeratorResult.Succeeded; + } + + if (SatisfiesGetEnumeratorPattern(syntax, ref builder, collectionExpr, isAsync, viaExtensionMethod: true, diagnostics, expr)) + { + return createPatternBasedEnumeratorResult(ref builder, collectionExpr, isAsync, viaExtensionMethod: true, diagnostics); + } + + return EnumeratorResult.FailedNotReported; + + EnumeratorResult createPatternBasedEnumeratorResult(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, bool viaExtensionMethod, DiagnosticBag diagnostics) + { + Debug.Assert((object)builder.GetEnumeratorInfo != null); + + Debug.Assert(!(viaExtensionMethod && builder.GetEnumeratorInfo.Method.Parameters.IsDefaultOrEmpty)); + + builder.CollectionType = viaExtensionMethod + ? builder.GetEnumeratorInfo.Method.Parameters[0].Type + : collectionExpr.Type; + + if (SatisfiesForEachPattern(syntax, ref builder, isAsync, diagnostics, expr)) + { + builder.ElementTypeWithAnnotations = ((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol).TypeWithAnnotations; + + GetDisposalInfoForEnumerator(syntax, ref builder, isAsync, diagnostics); + + return EnumeratorResult.Succeeded; + } + + MethodSymbol getEnumeratorMethod = builder.GetEnumeratorInfo.Method; + diagnostics.Add(isAsync ? ErrorCode.ERR_BadGetAsyncEnumerator : ErrorCode.ERR_BadGetEnumerator, expr.Location, getEnumeratorMethod.ReturnType, getEnumeratorMethod); + return EnumeratorResult.FailedAndReported; + } + } + + private EnumeratorResult SatisfiesIEnumerableInterfaces(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, DiagnosticBag diagnostics, TypeSymbol unwrappedCollectionExprType, SyntaxNode expr) + { + if (!AllInterfacesContainsIEnumerable(ref builder, unwrappedCollectionExprType, isAsync, diagnostics, out bool foundMultipleGenericIEnumerableInterfaces, expr)) + { + return EnumeratorResult.FailedNotReported; + } + + if (ReportConstantNullCollectionExpr(expr, collectionExpr, diagnostics)) + { + return EnumeratorResult.FailedAndReported; + } + + SyntaxNode errorLocationSyntax = expr; + + 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 (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, expr.Location, unwrappedCollectionExprType, 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.GetAsyncEnumerator has a default param, so let's fill it in + builder.GetEnumeratorInfo = BindDefaultArguments( + specificGetEnumeratorMethod, + extensionReceiverOpt: null, + expanded: false, + collectionExpr.Syntax, + diagnostics, + // C# 8 shipped allowing the CancellationToken of `IAsyncEnumerable.GetAsyncEnumerator` to be non-optional, + // filling in a default value in that case. https://github.com/dotnet/roslyn/issues/50182 tracks making + // this an error and breaking the scenario. + assertMissingParametersAreOptional: false); + + MethodSymbol currentPropertyGetter; + if (isAsync) + { + Debug.Assert(enumeratorType.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T))); + + MethodSymbol moveNextAsync = (MethodSymbol)GetWellKnownTypeMember(Compilation, 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 static bool ReportConstantNullCollectionExpr(SyntaxNode expr, BoundExpression collectionExpr, DiagnosticBag diagnostics) + { + if (collectionExpr.ConstantValue is { IsNull: true }) + { + // Spec seems to refer to null literals, but Dev10 reports anything known to be null. + diagnostics.Add(ErrorCode.ERR_NullNotValid, expr.Location); + return true; + } + return false; + } + + private void GetDisposalInfoForEnumerator(SyntaxNode syntax, ref ForEachEnumeratorInfo.Builder builder, bool isAsync, DiagnosticBag 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; + HashSet useSiteDiagnostics = null; + + // For async foreach, we don't do the runtime check + if ((!enumeratorType.IsSealed && !isAsync) || + this.Conversions.ClassifyImplicitConversionFromType(enumeratorType, + isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable), + ref useSiteDiagnostics).IsImplicit) + { + builder.NeedsDisposal = true; + } + else if (Compilation.IsFeatureEnabled(MessageID.IDS_FeatureUsingDeclarations) && + (enumeratorType.IsRefLikeType || isAsync)) + { + // if it wasn't directly convertable to IDisposable, see if it is pattern-disposable + // again, we throw away any binding diagnostics, and assume it's not disposable if we encounter errors + var patternDisposeDiags = new DiagnosticBag(); + var receiver = new BoundDisposableValuePlaceholder(syntax, enumeratorType); + MethodSymbol disposeMethod = TryFindDisposePatternMethod(receiver, syntax, isAsync, patternDisposeDiags); + if (disposeMethod is object) + { + Debug.Assert(!disposeMethod.IsExtensionMethod); + Debug.Assert(disposeMethod.ParameterRefKinds.IsDefaultOrEmpty); + + var argsBuilder = ArrayBuilder.GetInstance(disposeMethod.ParameterCount); + var argsToParams = default(ImmutableArray); + bool expanded = disposeMethod.HasParamsParameter(); + + BindDefaultArguments( + syntax, + disposeMethod.Parameters, + argsBuilder, + argumentRefKindsBuilder: null, + ref argsToParams, + out BitVector defaultArguments, + expanded, + enableCallerInfo: true, + diagnostics); + + builder.NeedsDisposal = true; + builder.PatternDisposeInfo = new MethodArgumentInfo(disposeMethod, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); + } + patternDisposeDiags.Free(); + } + + diagnostics.Add(syntax, useSiteDiagnostics); + } + + private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(SyntaxNode syntax, 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.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; + } + + /// + /// 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. + /// + /// Expression over which to iterate. + /// Populated with *warnings* if there are near misses. + /// + /// Builder to fill in. set if the pattern in satisfied. + /// True if the method was found (still have to verify that the return (i.e. enumerator) type is acceptable). + /// + /// Only adds warnings, so does not affect control flow (i.e. no need to check for failure). + /// + private bool SatisfiesGetEnumeratorPattern(SyntaxNode syntax, ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, bool viaExtensionMethod, DiagnosticBag diagnostics, SyntaxNode expr) + { + string methodName = isAsync ? GetAsyncEnumeratorMethodName : GetEnumeratorMethodName; + MethodArgumentInfo getEnumeratorInfo; + if (viaExtensionMethod) + { + getEnumeratorInfo = FindForEachPatternMethodViaExtension(syntax, expr, collectionExpr, methodName, diagnostics); + } + else + { + var lookupResult = LookupResult.GetInstance(); + getEnumeratorInfo = FindForEachPatternMethod(syntax, collectionExpr.Type, methodName, lookupResult, warningsOnly: true, diagnostics, isAsync, expr); + lookupResult.Free(); + } + + builder.GetEnumeratorInfo = getEnumeratorInfo; + return (object)getEnumeratorInfo != null; + } + + /// + /// Perform a lookup for the specified method on the specified type. Perform overload resolution + /// on the lookup results. + /// + /// Type to search. + /// Method to search for. + /// Passed in for reusability. + /// True if failures should result in warnings; false if they should result in errors. + /// Populated with binding diagnostics. + /// The desired method or null. + private MethodArgumentInfo FindForEachPatternMethod(SyntaxNode syntax, TypeSymbol patternType, string methodName, LookupResult lookupResult, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync, SyntaxNode expr) + { + Debug.Assert(lookupResult.IsClear); + + // Not using LookupOptions.MustBeInvocableMember because we don't want the corresponding lookup error. + // We filter out non-methods below. + HashSet useSiteDiagnostics = null; + this.LookupMembersInType( + lookupResult, + patternType, + methodName, + arity: 0, + basesBeingResolved: null, + options: LookupOptions.Default, + originalBinder: this, + diagnose: false, + useSiteDiagnostics: ref useSiteDiagnostics); + + diagnostics.Add(expr, useSiteDiagnostics); + + if (!lookupResult.IsMultiViable) + { + ReportPatternMemberLookupDiagnostics(lookupResult, patternType, methodName, warningsOnly, diagnostics, expr); + return null; + } + + ArrayBuilder candidateMethods = ArrayBuilder.GetInstance(); + + foreach (Symbol member in lookupResult.Symbols) + { + if (member.Kind != SymbolKind.Method) + { + candidateMethods.Free(); + + if (warningsOnly) + { + ReportEnumerableWarning(expr, 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, patternType, candidateMethods, warningsOnly, diagnostics, isAsync, expr); + + candidateMethods.Free(); + + return patternInfo; + } + + /// + /// The overload resolution portion of FindForEachPatternMethod. + /// If no arguments are passed in, then an empty argument list will be used. + /// + private MethodArgumentInfo PerformForEachPatternOverloadResolution(SyntaxNode syntax, TypeSymbol patternType, ArrayBuilder candidateMethods, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync, SyntaxNode expr) + { + var analyzedArguments = AnalyzedArguments.GetInstance(); + var typeArguments = ArrayBuilder.GetInstance(); + var overloadResolutionResult = OverloadResolutionResult.GetInstance(); + + HashSet useSiteDiagnostics = null; + // We create a dummy receiver of the invocation so MethodInvocationOverloadResolution knows it was invoked from an instance, not a type + var dummyReceiver = new BoundImplicitReceiver(expr, patternType); + this.OverloadResolution.MethodInvocationOverloadResolution( + methods: candidateMethods, + typeArguments: typeArguments, + receiver: dummyReceiver, + arguments: analyzedArguments, + result: overloadResolutionResult, + useSiteDiagnostics: ref useSiteDiagnostics); + diagnostics.Add(expr, useSiteDiagnostics); + + 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, expr.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 + { + var argsToParams = overloadResolutionResult.ValidResult.Result.ArgsToParamsOpt; + var expanded = overloadResolutionResult.ValidResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; + BindDefaultArguments( + syntax, + result.Parameters, + analyzedArguments.Arguments, + analyzedArguments.RefKinds, + ref argsToParams, + out BitVector defaultArguments, + expanded, + enableCallerInfo: true, + diagnostics); + + info = new MethodArgumentInfo(result, analyzedArguments.Arguments.ToImmutable(), argsToParams, defaultArguments, expanded); + } + } + else if (overloadResolutionResult.GetAllApplicableMembers() is var applicableMembers && applicableMembers.Length > 1) + { + if (warningsOnly) + { + diagnostics.Add(ErrorCode.WRN_PatternIsAmbiguous, expr.Location, patternType, MessageID.IDS_Collection.Localize(), + applicableMembers[0], applicableMembers[1]); + } + } + + overloadResolutionResult.Free(); + analyzedArguments.Free(); + typeArguments.Free(); + + return info; + } + + private MethodArgumentInfo FindForEachPatternMethodViaExtension(SyntaxNode syntax, SyntaxNode expr, BoundExpression collectionExpr, string methodName, DiagnosticBag diagnostics) + { + var analyzedArguments = AnalyzedArguments.GetInstance(); + + var methodGroupResolutionResult = this.BindExtensionMethod( + expr, + methodName, + analyzedArguments, + collectionExpr, + typeArgumentsWithAnnotations: default, + isMethodGroupConversion: false, + returnRefKind: default, + returnType: null); + + 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; + } + + HashSet useSiteDiagnostics = null; + var collectionConversion = this.Conversions.ClassifyConversionFromExpression(collectionExpr, result.Parameters[0].Type, ref useSiteDiagnostics); + diagnostics.Add(syntax, useSiteDiagnostics); + + // Unconditionally convert here, to match what we set the ConvertedExpression to in the main BoundForEachStatement node. + 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, expr.Location, collectionExpr.Type, MessageID.IDS_Collection.Localize(), + applicableMembers[0], applicableMembers[1]); + } + else if (overloadResolutionResult != null) + { + overloadResolutionResult.ReportDiagnostics( + binder: this, + location: expr.Location, + nodeOpt: expr, + diagnostics: diagnostics, + name: methodName, + receiver: null, + invokedExpression: expr, + arguments: methodGroupResolutionResult.AnalyzedArguments, + memberGroup: methodGroupResolutionResult.MethodGroup.Methods.ToImmutable(), + typeContainingConstructor: null, + delegateTypeBeingInvoked: null); + } + + methodGroupResolutionResult.Free(); + analyzedArguments.Free(); + return null; + } + + /// + /// 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). + /// + /// Must be non-null and contain a non-null GetEnumeratorMethod. + /// Will be populated with pattern diagnostics. + /// + /// True if the return type has suitable members. + /// + /// It seems that every failure path reports the same diagnostics, so that is left to the caller. + /// + private bool SatisfiesForEachPattern(SyntaxNode syntax, ref ForEachEnumeratorInfo.Builder builder, bool isAsync, DiagnosticBag diagnostics, SyntaxNode expr) + { + 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). + HashSet 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(expr, useSiteDiagnostics); + useSiteDiagnostics = null; + + if (!lookupResult.IsSingleViable) + { + ReportPatternMemberLookupDiagnostics(lookupResult, enumeratorType, CurrentPropertyName, warningsOnly: false, diagnostics: diagnostics, expr: expr); + 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(expr, 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 + + MethodArgumentInfo moveNextMethodCandidate = FindForEachPatternMethod(syntax, enumeratorType, + isAsync ? MoveNextAsyncMethodName : MoveNextMethodName, + lookupResult, warningsOnly: false, diagnostics, isAsync, expr); + + 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 static 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 expr, DiagnosticBag diagnostics, TypeSymbol enumeratorType, Symbol patternMemberCandidate) + { + HashSet useSiteDiagnostics = null; + if (this.IsAccessible(patternMemberCandidate, ref useSiteDiagnostics)) + { + diagnostics.Add(ErrorCode.WRN_PatternBadSignature, expr.Location, enumeratorType, MessageID.IDS_Collection.Localize(), patternMemberCandidate); + } + + diagnostics.Add(expr, useSiteDiagnostics); + } + + protected static bool IsIEnumerable(TypeSymbol type) + { + switch (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)); + } + + /// + /// 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. + /// + /// builder to fill in CollectionType. + /// Type to check. + /// + /// True if multiple T's are found. + /// + /// True if some IEnumerable is found (may still be ambiguous). + private bool AllInterfacesContainsIEnumerable(ref ForEachEnumeratorInfo.Builder builder, + TypeSymbol type, + bool isAsync, + DiagnosticBag diagnostics, + out bool foundMultiple, SyntaxNode expr) + { + HashSet useSiteDiagnostics = null; + NamedTypeSymbol implementedIEnumerable = ForEachLoopBinder.GetIEnumerableOfT(type, isAsync, Compilation, ref useSiteDiagnostics, out foundMultiple); + + // Prefer generic to non-generic, unless it is inaccessible. + if (((object)implementedIEnumerable == null) || !this.IsAccessible(implementedIEnumerable, ref useSiteDiagnostics)) + { + implementedIEnumerable = null; + + if (!isAsync) + { + var implementedNonGeneric = this.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable); + if ((object)implementedNonGeneric != null) + { + var conversion = this.Conversions.ClassifyImplicitConversionFromType(type, implementedNonGeneric, ref useSiteDiagnostics); + if (conversion.IsImplicit) + { + implementedIEnumerable = implementedNonGeneric; + } + } + } + } + + diagnostics.Add(expr, useSiteDiagnostics); + + builder.CollectionType = implementedIEnumerable; + return (object)implementedIEnumerable != null; + } + + /// + /// Report appropriate diagnostics when lookup of a pattern member (i.e. GetEnumerator, Current, or MoveNext) fails. + /// + /// Failed lookup result. + /// Type in which member was looked up. + /// Name of looked up member. + /// True if failures should result in warnings; false if they should result in errors. + /// Populated appropriately. + /// + private void ReportPatternMemberLookupDiagnostics(LookupResult lookupResult, TypeSymbol patternType, string memberName, bool warningsOnly, DiagnosticBag diagnostics, SyntaxNode expr) + { + if (lookupResult.Symbols.Any()) + { + if (warningsOnly) + { + ReportEnumerableWarning(expr, diagnostics, patternType, lookupResult.Symbols.First()); + } + else + { + lookupResult.Clear(); + + HashSet useSiteDiagnostics = null; + this.LookupMembersInType( + lookupResult, + patternType, + memberName, + arity: 0, + basesBeingResolved: null, + options: LookupOptions.Default, + originalBinder: this, + diagnose: true, + useSiteDiagnostics: ref useSiteDiagnostics); + + diagnostics.Add(expr, useSiteDiagnostics); + + if (lookupResult.Error != null) + { + diagnostics.Add(lookupResult.Error, expr.Location); + } + } + } + else if (!warningsOnly) + { + diagnostics.Add(ErrorCode.ERR_NoSuchMember, expr.Location, patternType, memberName); + } + } + + private MethodArgumentInfo GetParameterlessSpecialTypeMemberInfo(SpecialMember member, SyntaxNode syntax, DiagnosticBag 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; + } + + /// If method is an extension method, this must be non-null. + private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpression extensionReceiverOpt, bool expanded, SyntaxNode syntax, DiagnosticBag diagnostics, bool assertMissingParametersAreOptional = true) + { + Debug.Assert((extensionReceiverOpt != null) == method.IsExtensionMethod); + + if (method.ParameterCount == 0) + { + return MethodArgumentInfo.CreateParameterlessMethod(method); + } + + var argsBuilder = ArrayBuilder.GetInstance(method.ParameterCount); + + if (method.IsExtensionMethod) + { + argsBuilder.Add(extensionReceiverOpt); + } + + ImmutableArray argsToParams = default; + BindDefaultArguments( + syntax, + method.Parameters, + argsBuilder, + argumentRefKindsBuilder: null, + ref argsToParams, + defaultArguments: out BitVector defaultArguments, + expanded, + enableCallerInfo: true, + diagnostics, + assertMissingParametersAreOptional); + + return new MethodArgumentInfo(method, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 5a64305cffb0c..a81df1b0f2363 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -174,11 +175,266 @@ internal BoundPattern BindPattern( UnaryPatternSyntax p => BindUnaryPattern(p, inputType, inputValEscape, hasErrors, diagnostics, underIsPattern), RelationalPatternSyntax p => BindRelationalPattern(p, inputType, hasErrors, diagnostics), TypePatternSyntax p => BindTypePattern(p, inputType, hasErrors, diagnostics), + SlicePatternSyntax p => BindSlicePattern(p, inputType, permitDesignations, hasErrors, misplaced: true, diagnostics), _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), }; } - private BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSymbol inputType) + private BoundPattern BindSlicePattern( + SlicePatternSyntax node, + TypeSymbol inputType, + bool permitDesignations, + bool hasErrors, + bool misplaced, + DiagnosticBag diagnostics, + bool isEnumerable = false) + { + if (misplaced && !hasErrors) + { + diagnostics.Add(ErrorCode.ERR_MisplacedSlicePattern, node.Location); + hasErrors = true; + } + + BoundPattern? pattern = node.Pattern != null + ? BindPattern(node.Pattern, inputType, ExternalScope, permitDesignations, hasErrors, diagnostics) + : null; + + if (inputType.IsSZArray() || isEnumerable) + { + return new BoundSlicePattern(node, sliceMethodOpt: null, pattern, inputType, inputType, hasErrors); + } + + if (!inputType.IsErrorType() && + !inputType.IsArray() && + TryFindIndexOrRangeIndexerPattern(node, receiverOpt: null, inputType, argIsIndex: false, + lengthOrCountProperty: out _, out Symbol? patternSymbol, returnType: out _, diagnostics)) + { + return new BoundSlicePattern(node, (MethodSymbol)patternSymbol, pattern, inputType, inputType, hasErrors); + } + + if (!hasErrors) + { + diagnostics.Add(ErrorCode.ERR_UnsupportedTypeForSlicePattern, node.Location, inputType.ToDisplayString()); + hasErrors = true; + } + + return new BoundSlicePattern(node, null, pattern, inputType, inputType, hasErrors); + } + + private ImmutableArray BindListPatternSubpatterns( + RecursivePatternSyntax node, + TypeSymbol inputType, + TypeSymbol elementType, + bool permitDesignations, + ref bool hasErrors, + out bool sawSlice, + DiagnosticBag diagnostics, + bool isEnumerable = false) + { + sawSlice = false; + + if (node.PropertyPatternClause is null) + { + return ImmutableArray.Empty; + } + + var subpatterns = node.PropertyPatternClause.Subpatterns; + var builder = ArrayBuilder.GetInstance(subpatterns.Count); + foreach (SubpatternSyntax subpat in subpatterns) + { + if (subpat.NameColon != null) + { + diagnostics.Add(ErrorCode.ERR_NamedSubpatternInListPattern, subpat.NameColon.Location); + hasErrors = true; + } + + PatternSyntax pattern = subpat.Pattern; + BoundPattern boundPattern; + if (pattern is SlicePatternSyntax slice) + { + if (sawSlice) + { + diagnostics.Add(ErrorCode.ERR_MisplacedSlicePattern, slice.Location); + hasErrors = true; + } + + boundPattern = BindSlicePattern(slice, inputType, permitDesignations, hasErrors, misplaced: false, diagnostics, isEnumerable); + sawSlice = true; + } + else + { + boundPattern = BindPattern(pattern, elementType, ExternalScope, permitDesignations, hasErrors, diagnostics); + } + + builder.Add(boundPattern); + } + + return builder.ToImmutableAndFree(); + } + + private BoundListPatternInfo BindListPattern(RecursivePatternSyntax node, BoundPattern? lengthPattern, TypeSymbol inputType, bool permitDesignations, ref bool hasErrors, DiagnosticBag diagnostics) + { + if (inputType.IsSZArray()) + { + var arrayType = (ArrayTypeSymbol)inputType; + var subpatterns = BindListPatternSubpatterns(node, arrayType, arrayType.ElementType, permitDesignations, ref hasErrors, out bool sawSlice, diagnostics); + return new BoundListPatternWithArray(node, arrayType.ElementType, lengthPattern, subpatterns, sawSlice, hasErrors); + } + + if (!inputType.IsArray()) + { + using var _ = DiagnosticBag.GetInstance(out var ignoredDiagnostics); + if (TryFindIndexOrRangeIndexerPattern(node, receiverOpt: null, inputType, argIsIndex: true, + out PropertySymbol? lengthOrCountProperty, out Symbol? patternSymbol, out TypeSymbol? returnType, ignoredDiagnostics)) + { + var subpatterns = BindListPatternSubpatterns(node, inputType, returnType, permitDesignations, ref hasErrors, out bool sawSlice, diagnostics); + return new BoundListPatternWithRangeIndexerPattern(node, lengthOrCountProperty, (PropertySymbol)patternSymbol, returnType, lengthPattern, subpatterns, sawSlice, hasErrors); + } + + var builder = new ForEachEnumeratorInfo.Builder(); + BoundExpression receiver = new BoundImplicitReceiver(node, inputType); + if (EnumeratorResult.Succeeded == GetEnumeratorInfo(node, node, ref builder, ref receiver, isAsync: false, ignoredDiagnostics)) + { + var info = builder.Build(this.Flags); + var subpatterns = BindListPatternSubpatterns(node, inputType, info.ElementType, permitDesignations, ref hasErrors, out bool sawSlice, diagnostics, isEnumerable: true); + return new BoundListPatternWithEnumerablePattern(node, info, info.ElementType, lengthPattern, subpatterns, sawSlice, hasErrors); + } + } + + if (!inputType.IsErrorType() && !hasErrors) + { + diagnostics.Add(ErrorCode.ERR_UnsupportedTypeForListPattern, node.Location, inputType.ToDisplayString()); + hasErrors = true; + } + + { + var elementType = CreateErrorType(); + var subpatterns = BindListPatternSubpatterns(node, inputType, elementType, permitDesignations, ref hasErrors, out bool sawSlice, diagnostics); + return new BoundListPatternWithArray(node, elementType, lengthPattern, subpatterns, sawSlice, hasErrors: true); + } + } + +#if false // TODO(alrz) multidimensional arrays? + private BoundListPatternInfo BindListPatternWithArray( + PropertyPatternClauseSyntax node, ArrayTypeSymbol inputType, + bool permitDesignations, bool hasErrors, DiagnosticBag diagnostics, + ArrayBuilder sizes, int dimension, int rank) + { + Debug.Assert(rank > 0); + Debug.Assert(dimension > 0 && dimension <= rank); + + ImmutableArray subpatterns; + if (dimension == rank) + { + subpatterns = BindListPatternSubpatterns(node, inputType, inputType.ElementType, permitDesignations, ref hasErrors, diagnostics, out var sawSlice); + if (sawSlice) + { + throw new NotImplementedException(); + } + + var index = dimension - 1; + var size = sizes[index]; + if (size == -1) + { + sizes[index] = node.Subpatterns.Count; + } + else if (size != node.Subpatterns.Count) + { + diagnostics.Add(ErrorCode.ERR_ArrayInitializerIncorrectLength, node.Location, node); + hasErrors = true; + } + } + else + { + var builder = ArrayBuilder.GetInstance(); + foreach (var subpat in node.Subpatterns) + { + var pattern = subpat.Pattern; + BoundPattern boundPattern; + if (pattern is ListPatternSyntax nested) + { + boundPattern = BindListPatternWithArray(nested, inputType, permitDesignations, hasErrors, diagnostics, sizes, dimension + 1, rank); + } + else if (pattern is SlicePatternSyntax) + { + throw new NotImplementedException(); + } + else + { + boundPattern = BindPattern(pattern, inputType.ElementType, ExternalScope, permitDesignations, hasErrors, diagnostics); + if (!boundPattern.HasAnyErrors) + { + // PROTOTYPE(list-patterns): ERR_ListPatternExpected + Error(diagnostics, ErrorCode.ERR_ArrayInitializerExpected, pattern); + hasErrors = true; + } + } + builder.Add(boundPattern); + } + + subpatterns = builder.ToImmutableAndFree(); + } + + return new BoundListPatternWithArray( + syntax: node, + elementType: inputType.ElementType, + subpatterns: subpatterns, + hasSlice: false, + inputType: inputType, + narrowedType: inputType, + hasErrors: hasErrors); + } + + private BoundListPatternInfo BindListPatternWithArray( + PropertyPatternClauseSyntax node, ArrayTypeSymbol inputType, + bool permitDesignations, bool hasErrors, DiagnosticBag diagnostics) + { + var sizes = ArrayBuilder.GetInstance(inputType.Rank, -1); + var result = BindListPatternWithArray(node, inputType, permitDesignations, hasErrors, diagnostics, sizes, dimension: 1, inputType.Rank); + sizes.Free(); + return result; + } + + private static (int, bool) CalculateAndReportExpectedSizeIfNeeded(ListPatternSyntax node, int prevLength, bool prevExact, int newLength, bool newExact, DiagnosticBag diagnostics) + { + if (prevLength == -1) + { + return (newLength, newExact); + } + + // Given: + // + // X = the minimum or exact required length so far + // Y = the new length from current nested list pattern + // + // The expected size is calculated as follow: + // + // AtLeast(X) + AtLeast(Y) = AtLeast(Max(X, Y)) + // Exactly(X) + Exactly(Y) = Exactly(X) only if X==Y + // Exactly(X) + AtLeast(Y) = Exactly(X) only if X>=Y + // + var length = (prevExact, newExact) switch + { + (true, false) when prevLength >= newLength => prevLength, + (false, true) when prevLength <= newLength => newLength, + (false, false) => Math.Max(prevLength, newLength), + (true, true) when prevLength == newLength => newLength, + _ => -1 + }; + + if (length == -1) + { + var error = prevExact + ? ErrorCode.ERR_ArrayInitializerIncorrectLength // size prevLength expected + : ErrorCode.ERR_ArrayInitializerExpected; // at least prevLength expected + diagnostics.Add(error, node.GetLocation(), prevLength); + } + + return (length, prevExact | newExact); + } +#endif + + private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSymbol inputType) { return new BoundDiscardPattern(node, inputType: inputType, narrowedType: inputType); } @@ -223,7 +479,7 @@ internal BoundPattern BindConstantPatternWithFallbackToTypePattern( } } - private ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e) + private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e) { while (true) { @@ -739,11 +995,23 @@ private BoundPattern BindRecursivePattern( deconstructionSubpatterns = patternsBuilder.ToImmutableAndFree(); } - ImmutableArray properties = default; - if (node.PropertyPatternClause != null) - { - properties = BindPropertyPatternClause(node.PropertyPatternClause, declType, inputValEscape, permitDesignations, diagnostics, ref hasErrors); - } + BoundPattern? lengthPattern = node.LengthPatternClause is not null + ? BindPattern(node.LengthPatternClause.Pattern, this.Compilation.GetSpecialType(SpecialType.System_Int32), ExternalScope, permitDesignations, hasErrors, diagnostics) + : null; + + bool shouldBindPropertyPatternClauseAsListPattern = + (node.PropertyPatternClause is not null && + node.PropertyPatternClause.Subpatterns.Count > 0 && + node.PropertyPatternClause.Subpatterns[0].NameColon == null); + + // TODO(alrz) This does not accept [0]{P:P} + BoundListPatternInfo? listPatternInfo = lengthPattern is not null || shouldBindPropertyPatternClauseAsListPattern + ? BindListPattern(node, lengthPattern, inputType, permitDesignations, ref hasErrors, diagnostics) + : null; + + ImmutableArray properties = node.PropertyPatternClause is not null && !shouldBindPropertyPatternClauseAsListPattern + ? BindPropertyPatternClause(node.PropertyPatternClause, declType, inputValEscape, permitDesignations, diagnostics, ref hasErrors) + : default; BindPatternDesignation( node.Designation, declTypeWithAnnotations, inputValEscape, permitDesignations, typeSyntax, diagnostics, @@ -754,9 +1022,10 @@ boundDeclType is null && properties.IsDefaultOrEmpty && deconstructMethod is null && deconstructionSubpatterns.IsDefault; + return new BoundRecursivePattern( syntax: node, declaredType: boundDeclType, deconstructMethod: deconstructMethod, - deconstruction: deconstructionSubpatterns, properties: properties, variable: variableSymbol, + deconstruction: deconstructionSubpatterns, properties: properties, listPatternInfo: listPatternInfo, variable: variableSymbol, variableAccess: variableAccess, isExplicitNotNullTest: isExplicitNotNullTest, inputType: inputType, narrowedType: boundDeclType?.Type ?? inputType.StrippedType(), hasErrors: hasErrors); } @@ -1122,7 +1391,7 @@ private BoundPattern BindVarDesignation( return new BoundRecursivePattern( syntax: node, declaredType: null, deconstructMethod: deconstructMethod, - deconstruction: subPatterns.ToImmutableAndFree(), properties: default, variable: null, variableAccess: null, + deconstruction: subPatterns.ToImmutableAndFree(), properties: default, listPatternInfo: null, variable: null, variableAccess: null, isExplicitNotNullTest: false, inputType: inputType, narrowedType: inputType.StrippedType(), hasErrors: hasErrors); void addSubpatternsForTuple(ImmutableArray elementTypes) diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 631a742ea91a5..7bc3e316908e5 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -227,7 +227,10 @@ Tests scanAndSimplify(Tests tests) case Tests.False _: return tests; case Tests.One(BoundDagEvaluation e): - if (usedValues.Contains(e)) + // TODO(alrz) We emit unconditionally. There is probably some room for optimizations if values are disarded + if (e.Kind == BoundKind.DagNoOpEvaluation || + e.Kind == BoundKind.DagMethodEvaluation || + usedValues.Contains(e)) { if (e.Input.Source is { }) usedValues.Add(e.Input.Source); @@ -275,7 +278,8 @@ private Tests MakeTestsAndBindings( return MakeTestsAndBindingsForDeclarationPattern(input, declaration, out output, bindings); case BoundConstantPattern constant: return MakeTestsForConstantPattern(input, constant, out output); - case BoundDiscardPattern _: + case BoundDiscardPattern: + case BoundSlicePattern: output = input; return Tests.True.Instance; case BoundRecursivePattern recursive: @@ -296,6 +300,292 @@ private Tests MakeTestsAndBindings( } } + private Tests MakeTestsAndBindingsForListPattern(BoundDagTemp input, + BoundListPatternWithEnumerablePattern pattern, ArrayBuilder bindings) + { + var syntax = pattern.Syntax; + var subpatterns = pattern.Subpatterns; + var info = pattern.EnumeratorInfo; + + MethodSymbol moveNextMethod = info.MoveNextInfo.Method; + PropertySymbol currentProperty = (PropertySymbol)info.CurrentPropertyGetter.AssociatedSymbol; + + var tests = ArrayBuilder.GetInstance(); + + var enumeratorEvaluation = new BoundDagEnumeratorEvaluation(syntax, info, input); + tests.Add(new Tests.One(enumeratorEvaluation)); + var enumeratorTemp = new BoundDagTemp(syntax, info.GetEnumeratorInfo.Method.ReturnType, enumeratorEvaluation); + + int index = 0; + for (; index < subpatterns.Length; index++) + { + BoundPattern subpattern = subpatterns[index]; + if (subpattern is BoundSlicePattern slice) + { + if (slice.PatternOpt != null) + { + // TODO(alrz) error + throw new NotImplementedException(); + } + + tests.Add(MakeTestsAndBindingsForTrailingPatterns(index + 1, pattern, bindings, moveNextMethod, currentProperty, enumeratorTemp)); + goto done; + } + + var moveNextEvaluation = new BoundDagMethodEvaluation(syntax, moveNextMethod, index, enumeratorTemp); + tests.Add(new Tests.One(moveNextEvaluation)); + var moveNextTemp = new BoundDagTemp(syntax, moveNextMethod.ReturnType, moveNextEvaluation); + tests.Add(new Tests.One(new BoundDagValueTest(syntax, ConstantValue.Create(true), moveNextTemp))); + + var currentEvaluation = new BoundDagPropertyEvaluation(syntax, currentProperty, index, enumeratorTemp); + tests.Add(new Tests.One(currentEvaluation)); + var currentTemp = new BoundDagTemp(syntax, pattern.ElementType, currentEvaluation); + tests.Add(MakeTestsAndBindings(currentTemp, subpattern, bindings)); + } + + if (!pattern.HasSlice) + { + var moveNextEvaluation = new BoundDagMethodEvaluation(syntax, moveNextMethod, index, enumeratorTemp); + tests.Add(new Tests.One(moveNextEvaluation)); + var moveNextTemp = new BoundDagTemp(syntax, moveNextMethod.ReturnType, moveNextEvaluation); + tests.Add(new Tests.One(new BoundDagValueTest(syntax, ConstantValue.Create(false), moveNextTemp))); + } +done: + return Tests.AndSequence.Create(tests); + } + + private Tests MakeTestsAndBindingsForTrailingPatterns( + int index, BoundListPatternWithEnumerablePattern pattern, ArrayBuilder bindings, + MethodSymbol moveNextMethod, PropertySymbol currentProperty, BoundDagTemp enumeratorTemp) + { + var syntax = pattern.Syntax; + var tests = ArrayBuilder.GetInstance(); + + // TODO(alrz) TryGetNonEnumeratedCount + var countTemp = new BoundDagTemp(syntax, _compilation.GetSpecialType(SpecialType.System_Int32), enumeratorTemp.Source, index: 1); + + var (bufferType, bufferCtor, enqueueMethod, popMethod) = getWellKnownMembers(); + var (lengthTests, minLengthTest, maxLengthTest) = makeLengthTests(); + + var loopLabel = new BoundDagNoOpEvaluation(syntax, enumeratorTemp); + + var bufferCtorEvaluation = new BoundDagMethodEvaluation(syntax, bufferCtor, enumeratorTemp); + var bufferTemp = new BoundDagTemp(syntax, bufferType, bufferCtorEvaluation); + + addMoveNext(-1, test: true); + addOne(bufferCtorEvaluation); + addOne(loopLabel); + addEnqueue(); + tests.Add(maxLengthTest); + addMoveNext(-2, test: false, otherwiseGoTo: loopLabel); + tests.Add(minLengthTest); + tests.Add(lengthTests); + + var subpatterns = pattern.Subpatterns; + for (int i = subpatterns.Length - 1, j = 0; i >= index; i--, j++) + { + var popEvaluation = new BoundDagMethodEvaluation(syntax, popMethod, index: j, bufferTemp); + tests.Add(new Tests.One(popEvaluation)); + var popTemp = new BoundDagTemp(syntax, pattern.ElementType, popEvaluation); + tests.Add(MakeTestsAndBindings(popTemp, subpatterns[i], bindings)); + } + + return Tests.AndSequence.Create(tests); + + void addOne(BoundDagTest test) + { + tests.Add(new Tests.One(test)); + } + + void addEnqueue() + { + tests.Add(new Tests.One(new BoundDagMethodEvaluation(syntax, enqueueMethod, countTemp: countTemp, enumeratorTemp, currentProperty, index: 0, bufferTemp))); + } + + void addMoveNext(int index, bool test, BoundDagNoOpEvaluation? otherwiseGoTo = null) + { + var moveNextEvaluation = new BoundDagMethodEvaluation(syntax, moveNextMethod, index, enumeratorTemp); + tests.Add(new Tests.One(moveNextEvaluation)); + var moveNextTemp = new BoundDagTemp(syntax, moveNextMethod.ReturnType, moveNextEvaluation); + tests.Add(new Tests.One(new BoundDagValueTest(syntax, ConstantValue.Create(test), moveNextTemp) { Next = otherwiseGoTo })); + } + + (Tests lengthTests, Tests minLengthTest, Tests maxLengthTest) makeLengthTests() + { + Tests lengthTests = MakeRelationalTests(syntax, BinaryOperatorKind.IntGreaterThanOrEqual, ConstantValue.Create(pattern.Subpatterns.Length - index), countTemp); + Tests lengthPatternTests = pattern.LengthPattern is not null ? MakeTestsAndBindings(countTemp, pattern.LengthPattern, bindings) : Tests.True.Instance; + IValueSet? values1 = lengthTests.ComputeValueSet(); + IValueSet? values2 = lengthPatternTests.ComputeValueSet(); + if (values1 is null || values2 is null || values1.Intersect(values2) is not IValueSet { IsContiguous: true } lengthValueSet) + { + // TODO(alrz) error + throw new NotImplementedException(); + } + + (int minLength, int maxLength) = lengthValueSet.GetRange(); + Tests minLengthTest = MakeRelationalTests(syntax, BinaryOperatorKind.IntGreaterThanOrEqual, ConstantValue.Create(minLength), countTemp); + Tests maxLengthTest = MakeRelationalTests(syntax, BinaryOperatorKind.IntLessThanOrEqual, ConstantValue.Create(maxLength), countTemp); + return (lengthTests, minLengthTest, maxLengthTest); + } + + (NamedTypeSymbol bufferType, MethodSymbol bufferCtor, MethodSymbol enqueueMethod, MethodSymbol popMethod) getWellKnownMembers() + { + // TODO(alrz) Handle missing types and members + bufferType = _compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_Deque_T).Construct(pattern.ElementType); + bufferCtor = ((MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Deque_T__ctor))!.AsMember(bufferType); + enqueueMethod = ((MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Deque_T__Enqueue))!.AsMember(bufferType); + popMethod = ((MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Deque_T__Pop))!.AsMember(bufferType); + return (bufferType, bufferCtor, enqueueMethod, popMethod); + } + } + + private Tests MakeTestsAndBindingsForListPattern(BoundDagTemp input, BoundListPatternWithArray pattern, ArrayBuilder bindings) + { + Debug.Assert(input.Type.IsSZArray()); + var getLengthProperty = (PropertySymbol)this._compilation.GetSpecialTypeMember(SpecialMember.System_Array__Length); + return MakeTestsAndBindingsForListPattern(input, pattern, bindings, getLengthProperty, getItemProperty: null); + // TODO(alrz) md arrays +#if false + var syntax = pattern.Syntax; + var subpatterns = pattern.Subpatterns; + var subpatternCount = subpatterns.Length; + + Debug.Assert(isMultidimensional(subpatterns)); + var sizes = calculateSizes(); + var tests = ArrayBuilder.GetInstance(1 + sizes.Length * 2 + subpatternCount * 2); + MakeCheckNotNull(input, syntax, isExplicitTest: false, tests); + var lengthTempBuilder = ArrayBuilder.GetInstance(sizes.Length); + for (int i = 0; i < sizes.Length; ++i) + { + var lengthEvaluation = new BoundDagArrayLengthEvaluation(syntax, dimension: i, input); + tests.Add(new Tests.One(lengthEvaluation)); + var lengthTemp = new BoundDagTemp(syntax, this._compilation.GetSpecialType(SpecialType.System_Int32), lengthEvaluation); + lengthTempBuilder.Add(lengthTemp); + tests.Add(new Tests.One(new BoundDagValueTest(syntax, ConstantValue.Create(sizes[i]), lengthTemp))); + } + + var lengthTemps = lengthTempBuilder.ToImmutableAndFree(); + var indices = ArrayBuilder<(int, ImmutableArray)>.GetInstance(); + for (int i = 0; i < subpatternCount; i++) + { + indices.Push((i, ((BoundListPatternWithArray)subpatterns[i]).Subpatterns)); + makeTestsAndBindingsForListPatternWithArrayRecursive(indices); + } + Debug.Assert(indices.IsEmpty()); + indices.Free(); + + return Tests.AndSequence.Create(tests); + + ImmutableArray calculateSizes() + { + var builder = ArrayBuilder.GetInstance(((ArrayTypeSymbol)input.Type).Rank); + builder.Add(pattern.Subpatterns.Length); + + var first = pattern.Subpatterns[0]; + while (first is BoundListPatternWithArray nested) + { + builder.Add(nested.Subpatterns.Length); + first = nested.Subpatterns[0]; + } + return builder.ToImmutableAndFree(); + } + + void makeTestsAndBindingsForListPatternWithArrayRecursive(ArrayBuilder<(int Index, ImmutableArray Subpatterns)> indices) + { + var top = indices.Peek(); + var subpatterns = top.Subpatterns; + + if (isMultidimensional(subpatterns)) + { + for (int i = 0; i < subpatterns.Length; i++) + { + indices.Push((i, ((BoundListPatternWithArray)subpatterns[i]).Subpatterns)); + makeTestsAndBindingsForListPatternWithArrayRecursive(indices); + } + } + else + { + for (int i = 0; i < subpatterns.Length; i++) + { + var subpattern = subpatterns[i]; + + Debug.Assert(indices.Count == ((ArrayTypeSymbol)input.Type).Rank - 1); + + var indexBuilder = ArrayBuilder.GetInstance(indices.Count + 1); + foreach (var idx in indices) + indexBuilder.Add(idx.Index); + indexBuilder.Add(i); + + var arrayEvaluation = new BoundDagArrayEvaluation(syntax, lengthTemps, indexBuilder.ToImmutableAndFree(), input); + tests.Add(new Tests.One(arrayEvaluation)); + var arrayTemp = new BoundDagTemp(syntax, pattern.ElementType, arrayEvaluation); + tests.Add(MakeTestsAndBindings(arrayTemp, subpattern, bindings)); + } + } + indices.Pop(); + } + + static bool isMultidimensional(ImmutableArray subpatterns) + { + Debug.Assert(subpatterns.All((p) => p.Kind != BoundKind.ListPatternWithArray) || + subpatterns.All((p) => p.Kind == BoundKind.ListPatternWithArray), + "all or none should be nested"); + + return subpatterns.Length != 0 && subpatterns[0].Kind == BoundKind.ListPatternWithArray; + } +#endif + } + + private Tests MakeTestsAndBindingsForListPattern(BoundDagTemp input, BoundListPatternWithRangeIndexerPattern pattern, ArrayBuilder bindings) + { + return MakeTestsAndBindingsForListPattern(input, pattern, bindings, pattern.GetLengthProperty, pattern.GetItemProperty); + } + + private Tests MakeTestsAndBindingsForListPattern( + BoundDagTemp input, BoundListPatternInfo pattern, ArrayBuilder bindings, + PropertySymbol getLengthProperty, PropertySymbol? getItemProperty) + { + var syntax = pattern.Syntax; + var subpatternCount = pattern.Subpatterns.Length; + + var tests = ArrayBuilder.GetInstance(3 + subpatternCount * 2); + MakeCheckNotNull(input, syntax, isExplicitTest: false, tests); + + var lengthEvaluation = new BoundDagPropertyEvaluation(syntax, getLengthProperty, input); + tests.Add(new Tests.One(lengthEvaluation)); + var lengthTemp = new BoundDagTemp(syntax, this._compilation.GetSpecialType(SpecialType.System_Int32), lengthEvaluation); + var lengthTest = new Tests.One(pattern.HasSlice + ? new BoundDagRelationalTest(syntax, BinaryOperatorKind.IntGreaterThanOrEqual, ConstantValue.Create(subpatternCount - 1), lengthTemp) + : new BoundDagValueTest(syntax, ConstantValue.Create(subpatternCount), lengthTemp)); + tests.Add(lengthTest); + + int index = 0; + foreach (var subpattern in pattern.Subpatterns) + { + if (subpattern is BoundSlicePattern slice) + { + var currentIndex = index; + index -= subpatternCount - 1; + if (slice.PatternOpt is not null) + { + var sliceEvaluation = new BoundDagSliceEvaluation(syntax, slice.SliceMethodOpt, lengthTemp, currentIndex, index, input); + tests.Add(new Tests.One(sliceEvaluation)); + var sliceTemp = new BoundDagTemp(syntax, slice.SliceMethodOpt is null ? input.Type : slice.SliceMethodOpt.ReturnType, sliceEvaluation); + tests.Add(MakeTestsAndBindings(sliceTemp, slice.PatternOpt, bindings)); + } + + continue; + } + + var indexEvaluation = new BoundDagIndexEvaluation(syntax, getItemProperty, lengthTemp, index++, input); + tests.Add(new Tests.One(indexEvaluation)); + var indexTemp = new BoundDagTemp(syntax, pattern.ElementType, indexEvaluation); + tests.Add(MakeTestsAndBindings(indexTemp, subpattern, bindings)); + } + + return Tests.AndSequence.Create(tests); + } + private Tests MakeTestsAndBindingsForITuplePattern( BoundDagTemp input, BoundITuplePattern pattern, @@ -326,7 +616,7 @@ private Tests MakeTestsAndBindingsForITuplePattern( var getItemPropertyInput = OriginalInput(valueAsITuple, getItemProperty); for (int i = 0; i < patternLength; i++) { - var indexEvaluation = new BoundDagIndexEvaluation(syntax, getItemProperty, i, getItemPropertyInput); + var indexEvaluation = new BoundDagIndexEvaluation(syntax, getItemProperty, lengthTemp, i, getItemPropertyInput); tests.Add(new Tests.One(indexEvaluation)); var indexTemp = new BoundDagTemp(syntax, objectType, indexEvaluation); tests.Add(MakeTestsAndBindings(indexTemp, pattern.Subpatterns[i].Pattern, bindings)); @@ -552,6 +842,16 @@ private Tests MakeTestsAndBindingsForRecursivePattern( tests.Add(MakeTestsAndBindings(element, pattern, bindings)); } } + else if (recursive.ListPatternInfo != null) + { + tests.Add(recursive.ListPatternInfo switch + { + BoundListPatternWithArray list => MakeTestsAndBindingsForListPattern(input, list, bindings), + BoundListPatternWithEnumerablePattern list => MakeTestsAndBindingsForListPattern(input, list, bindings), + BoundListPatternWithRangeIndexerPattern list => MakeTestsAndBindingsForListPattern(input, list, bindings), + var v => throw ExceptionUtilities.UnexpectedValue(v.Kind) + }); + } if (recursive.VariableAccess != null) { @@ -608,21 +908,28 @@ private Tests MakeTestsAndBindingsForRelationalPattern( BoundRelationalPattern rel, out BoundDagTemp output) { - // check if the test is always true or always false var tests = ArrayBuilder.GetInstance(2); output = MakeConvertToType(input, rel.Syntax, rel.Value.Type!, isExplicitTest: false, tests); + tests.Add(MakeRelationalTests(rel.Syntax, rel.Relation, rel.ConstantValue, output, rel.HasErrors)); + return Tests.AndSequence.Create(tests); + } + + private static Tests MakeRelationalTests(SyntaxNode syntax, BinaryOperatorKind relation, ConstantValue value, BoundDagTemp input, bool hasErrors = false) + { + // check if the test is always true or always false var fac = ValueSetFactory.ForType(input.Type); - var values = fac?.Related(rel.Relation.Operator(), rel.ConstantValue); + var values = fac?.Related(relation.Operator(), value); if (values?.IsEmpty == true) { - tests.Add(Tests.False.Instance); + return Tests.False.Instance; } - else if (values?.Complement().IsEmpty != true) + + if (values?.Complement().IsEmpty != true) { - tests.Add(new Tests.One(new BoundDagRelationalTest(rel.Syntax, rel.Relation, rel.ConstantValue, output, rel.HasErrors))); + return new Tests.One(new BoundDagRelationalTest(syntax, relation, value, input, hasErrors)); } - return Tests.AndSequence.Create(tests); + return Tests.True.Instance; } private TypeSymbol ErrorType(string name = "") @@ -1645,6 +1952,8 @@ public abstract void Filter( out Tests whenTrue, out Tests whenFalse, ref bool foundExplicitNullTest); + + public abstract IValueSet? ComputeValueSet(); public virtual BoundDagTest ComputeSelectedTest() => throw ExceptionUtilities.Unreachable; public virtual Tests RemoveEvaluation(BoundDagEvaluation e) => this; public abstract string Dump(Func dump); @@ -1667,6 +1976,7 @@ public override void Filter( { whenTrue = whenFalse = this; } + public override IValueSet? ComputeValueSet() => ValueSetFactory.ForInt.AllValues; } /// @@ -1687,6 +1997,7 @@ public override void Filter( { whenTrue = whenFalse = this; } + public override IValueSet? ComputeValueSet() => ValueSetFactory.ForInt.NoValues; } /// @@ -1727,6 +2038,18 @@ public override void Filter( public override string Dump(Func dump) => dump(this.Test); public override bool Equals(object? obj) => this == obj || obj is One other && this.Test.Equals(other.Test); public override int GetHashCode() => this.Test.GetHashCode(); + public override IValueSet? ComputeValueSet() + { + var fac = ValueSetFactory.ForType(this.Test.Input.Type); + if (fac is null) + return null; + return this.Test switch + { + BoundDagRelationalTest test => fac.Related(test.Relation.Operator(), test.Value), + BoundDagValueTest test => fac.Related(BinaryOperatorKind.Equal, test.Value), + _ => fac.AllValues + }; + } } public sealed class Not : Tests @@ -1770,6 +2093,7 @@ public override void Filter( } public override bool Equals(object? obj) => this == obj || obj is Not n && Negated.Equals(n.Negated); public override int GetHashCode() => Hash.Combine(Negated.GetHashCode(), typeof(Not).GetHashCode()); + public override IValueSet? ComputeValueSet() => this.Negated.ComputeValueSet()?.Complement(); } public abstract class SequenceTests : Tests @@ -1858,6 +2182,7 @@ public static Tests Create(ArrayBuilder remainingTests) remainingTests.Free(); return result; } + public override BoundDagTest ComputeSelectedTest() { // Our simple heuristic is to perform the first test of the @@ -1884,6 +2209,18 @@ public override BoundDagTest ComputeSelectedTest() return RemainingTests[0].ComputeSelectedTest(); } + public override IValueSet? ComputeValueSet() + { + var result = RemainingTests[0].ComputeValueSet(); + for (var index = 1; index < RemainingTests.Length && result is not null; index++) + { + var values = RemainingTests[index].ComputeValueSet(); + if (values is null) + break; + result = result.Intersect(values); + } + return result; + } public override string Dump(Func dump) { return $"AND({string.Join(", ", RemainingTests.Select(t => t.Dump(dump)))})"; @@ -1928,6 +2265,18 @@ public static Tests Create(ArrayBuilder remainingTests) remainingTests.Free(); return result; } + public override IValueSet? ComputeValueSet() + { + var result = RemainingTests[0].ComputeValueSet(); + for (var index = 1; index < RemainingTests.Length && result is not null; index++) + { + var values = RemainingTests[index].ComputeValueSet(); + if (values is null) + break; + result = result.Union(values); + } + return result; + } public override string Dump(Func dump) { return $"OR({string.Join(", ", RemainingTests.Select(t => t.Dump(dump)))})"; diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 93613b04a58c9..8fd16654ddec4 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -4,12 +4,10 @@ #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; @@ -25,13 +23,6 @@ namespace Microsoft.CodeAnalysis.CSharp /// internal sealed class ForEachLoopBinder : LoopBinder { - private const string GetEnumeratorMethodName = WellKnownMemberNames.GetEnumeratorMethodName; - private const string CurrentPropertyName = WellKnownMemberNames.CurrentPropertyName; - private const string MoveNextMethodName = WellKnownMemberNames.MoveNextMethodName; - - private const string GetAsyncEnumeratorMethodName = WellKnownMemberNames.GetAsyncEnumeratorMethodName; - private const string MoveNextAsyncMethodName = WellKnownMemberNames.MoveNextAsyncMethodName; - private readonly CommonForEachStatementSyntax _syntax; private SourceLocalSymbol IterationVariable { @@ -539,7 +530,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, private bool GetAwaitDisposeAsyncInfo(ref ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics) { var awaitableType = builder.PatternDisposeInfo is null - ? this.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask, diagnostics, this._syntax) + ? this.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask, diagnostics, _syntax) : builder.PatternDisposeInfo.Method.ReturnType; bool hasErrors = false; @@ -590,42 +581,6 @@ private bool GetEnumeratorInfoAndInferCollectionElementType(ref ForEachEnumerato return gotInfo; } - private BoundExpression UnwrapCollectionExpressionIfNullable(BoundExpression collectionExpr, DiagnosticBag 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, - method: nullableValueGetter); - } - else - { - return new BoundBadExpression( - exprSyntax, - LookupResultKind.Empty, - ImmutableArray.Empty, - ImmutableArray.Create(collectionExpr), - collectionExprType.GetNullableUnderlyingType()) - { WasCompilerGenerated = true }; // Don't affect the type in the SemanticModel. - } - } - - return collectionExpr; - } - /// /// The spec describes an algorithm for finding the following types: /// 1) Collection type @@ -651,7 +606,7 @@ private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, ref Bo bool isAsync = IsAsync; builder.IsAsync = isAsync; - EnumeratorResult found = GetEnumeratorInfo(ref builder, ref collectionExpr, isAsync, diagnostics); + EnumeratorResult found = GetEnumeratorInfo(_syntax, _syntax.Expression, ref builder, ref collectionExpr, isAsync, diagnostics); switch (found) { case EnumeratorResult.Succeeded: @@ -674,7 +629,7 @@ private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, ref Bo // Retry with a different assumption about whether the foreach is async var ignoredBuilder = new ForEachEnumeratorInfo.Builder(); var ignoredDiagnostics = DiagnosticBag.GetInstance(); - bool wrongAsync = GetEnumeratorInfo(ref ignoredBuilder, ref collectionExpr, !isAsync, ignoredDiagnostics) == EnumeratorResult.Succeeded; + bool wrongAsync = GetEnumeratorInfo(_syntax, _syntax.Expression, ref ignoredBuilder, ref collectionExpr, !isAsync, ignoredDiagnostics) == EnumeratorResult.Succeeded; ignoredDiagnostics.Free(); var errorCode = wrongAsync @@ -686,832 +641,6 @@ private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, ref Bo return false; } - private enum EnumeratorResult - { - Succeeded, - FailedNotReported, - FailedAndReported - } - - private EnumeratorResult GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, ref BoundExpression collectionExpr, bool isAsync, DiagnosticBag diagnostics) - { - 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, _syntax.Expression.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, _syntax.Expression.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(builder, diagnostics, collectionExprType); - return EnumeratorResult.Succeeded; - } - - var unwrappedCollectionExpr = UnwrapCollectionExpressionIfNullable(collectionExpr, diagnostics); - var unwrappedCollectionExprType = unwrappedCollectionExpr.Type; - - if (SatisfiesGetEnumeratorPattern(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, _syntax.Expression.Location, unwrappedCollectionExprType, 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, _syntax.Expression.Location, unwrappedCollectionExprType, GetAsyncEnumeratorMethodName); - return EnumeratorResult.FailedAndReported; - } - - if (SatisfiesIEnumerableInterfaces(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(builder, diagnostics, collectionExprType); - return EnumeratorResult.Succeeded; - } - - if (SatisfiesGetEnumeratorPattern(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, DiagnosticBag diagnostics) - { - Debug.Assert((object)builder.GetEnumeratorInfo != null); - - Debug.Assert(!(viaExtensionMethod && builder.GetEnumeratorInfo.Method.Parameters.IsDefaultOrEmpty)); - - builder.CollectionType = viaExtensionMethod - ? builder.GetEnumeratorInfo.Method.Parameters[0].Type - : collectionExpr.Type; - - if (SatisfiesForEachPattern(ref builder, isAsync, diagnostics)) - { - builder.ElementTypeWithAnnotations = ((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol).TypeWithAnnotations; - - GetDisposalInfoForEnumerator(ref builder, collectionExpr, isAsync, diagnostics); - - return EnumeratorResult.Succeeded; - } - - MethodSymbol getEnumeratorMethod = builder.GetEnumeratorInfo.Method; - diagnostics.Add(isAsync ? ErrorCode.ERR_BadGetAsyncEnumerator : ErrorCode.ERR_BadGetEnumerator, _syntax.Expression.Location, getEnumeratorMethod.ReturnType, getEnumeratorMethod); - return EnumeratorResult.FailedAndReported; - } - } - - private EnumeratorResult SatisfiesIEnumerableInterfaces(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, DiagnosticBag diagnostics, TypeSymbol unwrappedCollectionExprType) - { - if (!AllInterfacesContainsIEnumerable(ref builder, unwrappedCollectionExprType, isAsync, diagnostics, out bool foundMultipleGenericIEnumerableInterfaces)) - { - return EnumeratorResult.FailedNotReported; - } - - if (ReportConstantNullCollectionExpr(collectionExpr, diagnostics)) - { - return EnumeratorResult.FailedAndReported; - } - - CSharpSyntaxNode errorLocationSyntax = _syntax.Expression; - - 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 (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, _syntax.Expression.Location, unwrappedCollectionExprType, 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.GetAsyncEnumerator has a default param, so let's fill it in - builder.GetEnumeratorInfo = BindDefaultArguments( - specificGetEnumeratorMethod, - extensionReceiverOpt: null, - expanded: false, - collectionExpr.Syntax, - diagnostics, - // C# 8 shipped allowing the CancellationToken of `IAsyncEnumerable.GetAsyncEnumerator` to be non-optional, - // filling in a default value in that case. https://github.com/dotnet/roslyn/issues/50182 tracks making - // this an error and breaking the scenario. - assertMissingParametersAreOptional: false); - - MethodSymbol currentPropertyGetter; - if (isAsync) - { - Debug.Assert(enumeratorType.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T))); - - MethodSymbol moveNextAsync = (MethodSymbol)GetWellKnownTypeMember(Compilation, 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, DiagnosticBag diagnostics) - { - if (collectionExpr.ConstantValue is { IsNull: true }) - { - // Spec seems to refer to null literals, but Dev10 reports anything known to be null. - diagnostics.Add(ErrorCode.ERR_NullNotValid, _syntax.Expression.Location); - return true; - } - return false; - } - - private void GetDisposalInfoForEnumerator(ref ForEachEnumeratorInfo.Builder builder, BoundExpression expr, bool isAsync, DiagnosticBag 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; - HashSet useSiteDiagnostics = null; - - // For async foreach, we don't do the runtime check - if ((!enumeratorType.IsSealed && !isAsync) || - this.Conversions.ClassifyImplicitConversionFromType(enumeratorType, - isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable), - ref useSiteDiagnostics).IsImplicit) - { - builder.NeedsDisposal = true; - } - else if (Compilation.IsFeatureEnabled(MessageID.IDS_FeatureUsingDeclarations) && - (enumeratorType.IsRefLikeType || isAsync)) - { - // if it wasn't directly convertable to IDisposable, see if it is pattern-disposable - // again, we throw away any binding diagnostics, and assume it's not disposable if we encounter errors - var patternDisposeDiags = new DiagnosticBag(); - var receiver = new BoundDisposableValuePlaceholder(_syntax, enumeratorType); - MethodSymbol disposeMethod = TryFindDisposePatternMethod(receiver, _syntax, isAsync, patternDisposeDiags); - if (disposeMethod is object) - { - Debug.Assert(!disposeMethod.IsExtensionMethod); - Debug.Assert(disposeMethod.ParameterRefKinds.IsDefaultOrEmpty); - - var argsBuilder = ArrayBuilder.GetInstance(disposeMethod.ParameterCount); - var argsToParams = default(ImmutableArray); - bool expanded = disposeMethod.HasParamsParameter(); - - BindDefaultArguments( - _syntax, - disposeMethod.Parameters, - argsBuilder, - argumentRefKindsBuilder: null, - ref argsToParams, - out BitVector defaultArguments, - expanded, - enableCallerInfo: true, - diagnostics); - - builder.NeedsDisposal = true; - builder.PatternDisposeInfo = new MethodArgumentInfo(disposeMethod, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); - } - patternDisposeDiags.Free(); - } - - diagnostics.Add(_syntax, useSiteDiagnostics); - } - - 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.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; - } - - /// - /// 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. - /// - /// Expression over which to iterate. - /// Populated with *warnings* if there are near misses. - /// Builder to fill in. set if the pattern in satisfied. - /// True if the method was found (still have to verify that the return (i.e. enumerator) type is acceptable). - /// - /// Only adds warnings, so does not affect control flow (i.e. no need to check for failure). - /// - private bool SatisfiesGetEnumeratorPattern(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, bool isAsync, bool viaExtensionMethod, DiagnosticBag diagnostics) - { - string methodName = isAsync ? GetAsyncEnumeratorMethodName : GetEnumeratorMethodName; - MethodArgumentInfo getEnumeratorInfo; - if (viaExtensionMethod) - { - getEnumeratorInfo = FindForEachPatternMethodViaExtension(collectionExpr, methodName, diagnostics); - } - else - { - var lookupResult = LookupResult.GetInstance(); - getEnumeratorInfo = FindForEachPatternMethod(collectionExpr.Type, methodName, lookupResult, warningsOnly: true, diagnostics, isAsync); - lookupResult.Free(); - } - - builder.GetEnumeratorInfo = getEnumeratorInfo; - return (object)getEnumeratorInfo != null; - } - - /// - /// Perform a lookup for the specified method on the specified type. Perform overload resolution - /// on the lookup results. - /// - /// Type to search. - /// Method to search for. - /// Passed in for reusability. - /// True if failures should result in warnings; false if they should result in errors. - /// Populated with binding diagnostics. - /// The desired method or null. - private MethodArgumentInfo FindForEachPatternMethod(TypeSymbol patternType, string methodName, LookupResult lookupResult, bool warningsOnly, DiagnosticBag 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. - HashSet useSiteDiagnostics = null; - this.LookupMembersInType( - lookupResult, - patternType, - methodName, - arity: 0, - basesBeingResolved: null, - options: LookupOptions.Default, - originalBinder: this, - diagnose: false, - useSiteDiagnostics: ref useSiteDiagnostics); - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - if (!lookupResult.IsMultiViable) - { - ReportPatternMemberLookupDiagnostics(lookupResult, patternType, methodName, warningsOnly, diagnostics); - return null; - } - - ArrayBuilder candidateMethods = ArrayBuilder.GetInstance(); - - foreach (Symbol member in lookupResult.Symbols) - { - if (member.Kind != SymbolKind.Method) - { - candidateMethods.Free(); - - if (warningsOnly) - { - ReportEnumerableWarning(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(patternType, candidateMethods, warningsOnly, diagnostics, isAsync); - - candidateMethods.Free(); - - return patternInfo; - } - - /// - /// The overload resolution portion of FindForEachPatternMethod. - /// If no arguments are passed in, then an empty argument list will be used. - /// - private MethodArgumentInfo PerformForEachPatternOverloadResolution(TypeSymbol patternType, ArrayBuilder candidateMethods, bool warningsOnly, DiagnosticBag diagnostics, bool isAsync) - { - var analyzedArguments = AnalyzedArguments.GetInstance(); - var typeArguments = ArrayBuilder.GetInstance(); - var overloadResolutionResult = OverloadResolutionResult.GetInstance(); - - HashSet useSiteDiagnostics = null; - // We create a dummy receiver of the invocation so MethodInvocationOverloadResolution knows it was invoked from an instance, not a type - var dummyReceiver = new BoundImplicitReceiver(_syntax.Expression, patternType); - this.OverloadResolution.MethodInvocationOverloadResolution( - methods: candidateMethods, - typeArguments: typeArguments, - receiver: dummyReceiver, - arguments: analyzedArguments, - result: overloadResolutionResult, - useSiteDiagnostics: ref useSiteDiagnostics); - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - 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, _syntax.Expression.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 - { - var argsToParams = overloadResolutionResult.ValidResult.Result.ArgsToParamsOpt; - var expanded = overloadResolutionResult.ValidResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - BindDefaultArguments( - _syntax, - result.Parameters, - analyzedArguments.Arguments, - analyzedArguments.RefKinds, - ref argsToParams, - out BitVector defaultArguments, - expanded, - enableCallerInfo: true, - diagnostics); - - info = new MethodArgumentInfo(result, analyzedArguments.Arguments.ToImmutable(), argsToParams, defaultArguments, expanded); - } - } - else if (overloadResolutionResult.GetAllApplicableMembers() is var applicableMembers && applicableMembers.Length > 1) - { - if (warningsOnly) - { - diagnostics.Add(ErrorCode.WRN_PatternIsAmbiguous, _syntax.Expression.Location, patternType, MessageID.IDS_Collection.Localize(), - applicableMembers[0], applicableMembers[1]); - } - } - - overloadResolutionResult.Free(); - analyzedArguments.Free(); - typeArguments.Free(); - - return info; - } - - private MethodArgumentInfo FindForEachPatternMethodViaExtension(BoundExpression collectionExpr, string methodName, DiagnosticBag diagnostics) - { - var analyzedArguments = AnalyzedArguments.GetInstance(); - - var methodGroupResolutionResult = this.BindExtensionMethod( - _syntax.Expression, - methodName, - analyzedArguments, - collectionExpr, - typeArgumentsWithAnnotations: default, - isMethodGroupConversion: false, - returnRefKind: default, - returnType: null); - - 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; - } - - HashSet useSiteDiagnostics = null; - var collectionConversion = this.Conversions.ClassifyConversionFromExpression(collectionExpr, result.Parameters[0].Type, ref useSiteDiagnostics); - diagnostics.Add(_syntax, useSiteDiagnostics); - - // Unconditionally convert here, to match what we set the ConvertedExpression to in the main BoundForEachStatement node. - 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, _syntax.Expression.Location, collectionExpr.Type, MessageID.IDS_Collection.Localize(), - applicableMembers[0], applicableMembers[1]); - } - else if (overloadResolutionResult != null) - { - overloadResolutionResult.ReportDiagnostics( - binder: this, - location: _syntax.Expression.Location, - nodeOpt: _syntax.Expression, - diagnostics: diagnostics, - name: methodName, - receiver: null, - invokedExpression: _syntax.Expression, - arguments: methodGroupResolutionResult.AnalyzedArguments, - memberGroup: methodGroupResolutionResult.MethodGroup.Methods.ToImmutable(), - typeContainingConstructor: null, - delegateTypeBeingInvoked: null); - } - - methodGroupResolutionResult.Free(); - analyzedArguments.Free(); - return null; - } - - /// - /// 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). - /// - /// Must be non-null and contain a non-null GetEnumeratorMethod. - /// Will be populated with pattern diagnostics. - /// True if the return type has suitable members. - /// - /// It seems that every failure path reports the same diagnostics, so that is left to the caller. - /// - private bool SatisfiesForEachPattern(ref ForEachEnumeratorInfo.Builder builder, bool isAsync, DiagnosticBag 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). - HashSet 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 - - MethodArgumentInfo moveNextMethodCandidate = FindForEachPatternMethod(enumeratorType, - isAsync ? MoveNextAsyncMethodName : 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(DiagnosticBag diagnostics, TypeSymbol enumeratorType, Symbol patternMemberCandidate) - { - HashSet useSiteDiagnostics = null; - if (this.IsAccessible(patternMemberCandidate, ref useSiteDiagnostics)) - { - diagnostics.Add(ErrorCode.WRN_PatternBadSignature, _syntax.Expression.Location, enumeratorType, MessageID.IDS_Collection.Localize(), patternMemberCandidate); - } - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - } - - private 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)); - } - - /// - /// 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. - /// - /// builder to fill in CollectionType. - /// Type to check. - /// - /// True if multiple T's are found. - /// True if some IEnumerable is found (may still be ambiguous). - private bool AllInterfacesContainsIEnumerable( - ref ForEachEnumeratorInfo.Builder builder, - TypeSymbol type, - bool isAsync, - DiagnosticBag diagnostics, - out bool foundMultiple) - { - HashSet useSiteDiagnostics = null; - NamedTypeSymbol implementedIEnumerable = GetIEnumerableOfT(type, isAsync, Compilation, ref useSiteDiagnostics, out foundMultiple); - - // Prefer generic to non-generic, unless it is inaccessible. - if (((object)implementedIEnumerable == null) || !this.IsAccessible(implementedIEnumerable, ref useSiteDiagnostics)) - { - implementedIEnumerable = null; - - if (!isAsync) - { - var implementedNonGeneric = this.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable); - if ((object)implementedNonGeneric != null) - { - var conversion = this.Conversions.ClassifyImplicitConversionFromType(type, implementedNonGeneric, ref useSiteDiagnostics); - if (conversion.IsImplicit) - { - implementedIEnumerable = implementedNonGeneric; - } - } - } - } - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - builder.CollectionType = implementedIEnumerable; - return (object)implementedIEnumerable != null; - } - internal static NamedTypeSymbol GetIEnumerableOfT(TypeSymbol type, bool isAsync, CSharpCompilation compilation, ref HashSet useSiteDiagnostics, out bool foundMultiple) { NamedTypeSymbol implementedIEnumerable = null; @@ -1571,52 +700,6 @@ internal static bool IsIEnumerableT(TypeSymbol type, bool isAsync, CSharpCompila } } - /// - /// Report appropriate diagnostics when lookup of a pattern member (i.e. GetEnumerator, Current, or MoveNext) fails. - /// - /// Failed lookup result. - /// Type in which member was looked up. - /// Name of looked up member. - /// True if failures should result in warnings; false if they should result in errors. - /// Populated appropriately. - private void ReportPatternMemberLookupDiagnostics(LookupResult lookupResult, TypeSymbol patternType, string memberName, bool warningsOnly, DiagnosticBag diagnostics) - { - if (lookupResult.Symbols.Any()) - { - if (warningsOnly) - { - ReportEnumerableWarning(diagnostics, patternType, lookupResult.Symbols.First()); - } - else - { - lookupResult.Clear(); - - HashSet useSiteDiagnostics = null; - this.LookupMembersInType( - lookupResult, - patternType, - memberName, - arity: 0, - basesBeingResolved: null, - options: LookupOptions.Default, - originalBinder: this, - diagnose: true, - useSiteDiagnostics: ref useSiteDiagnostics); - - diagnostics.Add(_syntax.Expression, useSiteDiagnostics); - - if (lookupResult.Error != null) - { - diagnostics.Add(lookupResult.Error, _syntax.Expression.Location); - } - } - } - else if (!warningsOnly) - { - diagnostics.Add(ErrorCode.ERR_NoSuchMember, _syntax.Expression.Location, patternType, memberName); - } - } - internal override ImmutableArray GetDeclaredLocalsForScope(SyntaxNode scopeDesignator) { if (_syntax == scopeDesignator) @@ -1639,47 +722,5 @@ internal override SyntaxNode ScopeDesignator return _syntax; } } - - private MethodArgumentInfo GetParameterlessSpecialTypeMemberInfo(SpecialMember member, SyntaxNode syntax, DiagnosticBag 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; - } - - /// If method is an extension method, this must be non-null. - private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpression extensionReceiverOpt, bool expanded, SyntaxNode syntax, DiagnosticBag diagnostics, bool assertMissingParametersAreOptional = true) - { - Debug.Assert((extensionReceiverOpt != null) == method.IsExtensionMethod); - - if (method.ParameterCount == 0) - { - return MethodArgumentInfo.CreateParameterlessMethod(method); - } - - var argsBuilder = ArrayBuilder.GetInstance(method.ParameterCount); - - if (method.IsExtensionMethod) - { - argsBuilder.Add(extensionReceiverOpt); - } - - ImmutableArray argsToParams = default; - BindDefaultArguments( - syntax, - method.Parameters, - argsBuilder, - argumentRefKindsBuilder: null, - ref argsToParams, - defaultArguments: out BitVector defaultArguments, - expanded, - enableCallerInfo: true, - diagnostics, - assertMissingParametersAreOptional); - - return new MethodArgumentInfo(method, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); - } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs index 79767a5bf915d..7a2904e7c9ecc 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; +using System.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -15,21 +16,27 @@ public virtual bool Equals(BoundDagEvaluation other) return this == other || this.Kind == other.Kind && this.Input.Equals(other.Input) && - this.Symbol.Equals(other.Symbol, TypeCompareKind.AllIgnoreOptions); + Symbol.Equals(this.Symbol, other.Symbol, TypeCompareKind.AllIgnoreOptions); } - private Symbol Symbol + private Symbol? Symbol { get { - switch (this) + return this switch { - case BoundDagFieldEvaluation e: return e.Field.CorrespondingTupleField ?? e.Field; - case BoundDagPropertyEvaluation e: return e.Property; - case BoundDagTypeEvaluation e: return e.Type; - case BoundDagDeconstructEvaluation e: return e.DeconstructMethod; - case BoundDagIndexEvaluation e: return e.Property; - default: throw ExceptionUtilities.UnexpectedValue(this.Kind); - } + BoundDagFieldEvaluation e => e.Field.CorrespondingTupleField ?? e.Field, + BoundDagPropertyEvaluation e => e.Property, + BoundDagTypeEvaluation e => e.Type, + BoundDagDeconstructEvaluation e => e.DeconstructMethod, + BoundDagMethodEvaluation e => e.Method, + BoundDagEnumeratorEvaluation e => e.EnumeratorInfo.GetEnumeratorInfo.Method, + BoundDagIndexEvaluation e => e.PropertyOpt, + BoundDagSliceEvaluation e => e.SliceMethodOpt, + BoundDagArrayEvaluation or + BoundDagArrayLengthEvaluation or + BoundDagNoOpEvaluation => null, + _ => throw ExceptionUtilities.UnexpectedValue(this.Kind) + }; } } @@ -50,4 +57,65 @@ public override bool Equals(BoundDagEvaluation obj) this.Index == ((BoundDagIndexEvaluation)obj).Index; } } + + partial class BoundDagSliceEvaluation + { + public override int GetHashCode() => base.GetHashCode() ^ this.StartIndex ^ this.EndIndex; + public override bool Equals(BoundDagEvaluation obj) + { + return this == obj || + base.Equals(obj) && + // base.Equals checks the kind field, so the following cast is safe + (BoundDagSliceEvaluation)obj is var e && + this.StartIndex == e.StartIndex && this.EndIndex == e.EndIndex; + } + } + + partial class BoundDagArrayEvaluation + { + public override int GetHashCode() => base.GetHashCode() ^ Hash.CombineValues(this.Indices); + public override bool Equals(BoundDagEvaluation obj) + { + return this == obj || + base.Equals(obj) && + // base.Equals checks the kind field, so the following cast is safe + this.Indices.SequenceEqual(((BoundDagArrayEvaluation)obj).Indices); + } + } + + partial class BoundDagArrayLengthEvaluation + { + public override int GetHashCode() => base.GetHashCode() ^ this.Dimension; + public override bool Equals(BoundDagEvaluation obj) + { + return this == obj || + base.Equals(obj) && + // base.Equals checks the kind field, so the following cast is safe + this.Dimension == ((BoundDagArrayLengthEvaluation)obj).Dimension; + } + } + + partial class BoundDagMethodEvaluation + { + public override int GetHashCode() => base.GetHashCode() ^ this.Index; + public override bool Equals(BoundDagEvaluation obj) + { + return this == obj || + base.Equals(obj) && + // base.Equals checks the kind field, so the following cast is safe + this.Index == ((BoundDagMethodEvaluation)obj).Index; + } + } + + partial class BoundDagPropertyEvaluation + { + public override int GetHashCode() => base.GetHashCode() ^ this.Index; + public override bool Equals(BoundDagEvaluation obj) + { + return this == obj || + base.Equals(obj) && + // base.Equals checks the kind field, so the following cast is safe + this.Index == ((BoundDagPropertyEvaluation)obj).Index; + } + } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs index be06e6274bfbd..a3ef02c6c62b7 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDagTest.cs @@ -17,6 +17,8 @@ private bool Equals(BoundDagTest? other) return false; if (this == other) return true; + if (!Equals(this.Next, other.Next)) + return false; switch (this, other) { @@ -37,7 +39,13 @@ private bool Equals(BoundDagTest? other) public override int GetHashCode() { - return Hash.Combine(Kind.GetHashCode(), Input.GetHashCode()); + return Hash.Combine(Hash.Combine(Kind.GetHashCode(), Input.GetHashCode()), Next?.GetHashCode() ?? 0); } + + /// + /// To represent loops in DAG lowering, we keep an alternative + /// node that we will jump to after a test failure. + /// + public BoundDagNoOpEvaluation? Next { get; init; } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 2ab1b76177e98..e639640902359 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1377,8 +1377,9 @@ - Input is the input to the decision point. + + @@ -1420,11 +1421,40 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2059,7 +2089,9 @@ + + @@ -2108,6 +2140,28 @@ + + + + + + + + + + + + + + + + + + + + + + +