diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 570319b7e1a86..2620ca8d181c4 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -201,8 +201,13 @@ private BoundIndexerAccess BindIndexerDefaultArguments(BoundIndexerAccess indexe { parameters = parameters.RemoveAt(parameters.Length - 1); } + + BitVector defaultArguments = default; Debug.Assert(parameters.Length == indexerAccess.Indexer.Parameters.Length); - BindDefaultArguments(indexerAccess.Syntax, parameters, argumentsBuilder, refKindsBuilderOpt, ref argsToParams, out var defaultArguments, indexerAccess.Expanded, enableCallerInfo: true, diagnostics); + if (indexerAccess.OriginalIndexersOpt.IsDefault) + { + BindDefaultArguments(indexerAccess.Syntax, parameters, argumentsBuilder, refKindsBuilderOpt, ref argsToParams, out defaultArguments, indexerAccess.Expanded, enableCallerInfo: true, diagnostics); + } indexerAccess = indexerAccess.Update( indexerAccess.ReceiverOpt, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index fe936ace1042b..76118c5a2ed32 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1162,90 +1162,6 @@ private static SourceLocation GetCallerLocation(SyntaxNode syntax) return new SourceLocation(token); } - internal BoundExpression BindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol containingMember, bool enableCallerInfo, DiagnosticBag diagnostics) - { - Debug.Assert(parameter.IsOptional); - - TypeSymbol parameterType = parameter.Type; - if (Flags.Includes(BinderFlags.ParameterDefaultValue)) - { - // This is only expected to occur in recursive error scenarios, for example: `object F(object param = F()) { }` - // We return a non-error expression here to ensure ERR_DefaultValueMustBeConstant (or another appropriate diagnostics) is produced by the caller. - return new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; - } - - var defaultConstantValue = parameter.ExplicitDefaultConstantValue switch - { - // Bad default values are implicitly replaced with default(T) at call sites. - { IsBad: true } => ConstantValue.Null, - var constantValue => constantValue - }; - Debug.Assert((object?)defaultConstantValue != ConstantValue.Unset); - - var callerSourceLocation = enableCallerInfo ? GetCallerLocation(syntax) : null; - BoundExpression defaultValue; - if (callerSourceLocation is object && parameter.IsCallerLineNumber) - { - int line = callerSourceLocation.SourceTree.GetDisplayLineNumber(callerSourceLocation.SourceSpan); - defaultValue = new BoundLiteral(syntax, ConstantValue.Create(line), Compilation.GetSpecialType(SpecialType.System_Int32)) { WasCompilerGenerated = true }; - } - else if (callerSourceLocation is object && parameter.IsCallerFilePath) - { - string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, Compilation.Options.SourceReferenceResolver); - defaultValue = new BoundLiteral(syntax, ConstantValue.Create(path), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; - } - else if (callerSourceLocation is object && parameter.IsCallerMemberName) - { - var memberName = containingMember.GetMemberCallerName(); - defaultValue = new BoundLiteral(syntax, ConstantValue.Create(memberName), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; - } - else if (defaultConstantValue == ConstantValue.NotAvailable) - { - // There is no constant value given for the parameter in source/metadata. - if (parameterType.IsDynamic() || parameterType.SpecialType == SpecialType.System_Object) - { - // We have something like M([Optional] object x). We have special handling for such situations. - defaultValue = GetDefaultParameterSpecialNoConversion(syntax, parameter, diagnostics); - } - else - { - // The argument to M([Optional] int x) becomes default(int) - defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; - } - } - else if (defaultConstantValue.IsNull) - { - defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; - } - else - { - TypeSymbol constantType = Compilation.GetSpecialType(defaultConstantValue.SpecialType); - defaultValue = new BoundLiteral(syntax, defaultConstantValue, constantType) { WasCompilerGenerated = true }; - } - - HashSet? useSiteDiagnostics = null; - Conversion conversion = Conversions.ClassifyConversionFromExpression(defaultValue, parameterType, ref useSiteDiagnostics); - diagnostics.Add(syntax, useSiteDiagnostics); - - if (!conversion.IsValid && defaultConstantValue is { SpecialType: SpecialType.System_Decimal or SpecialType.System_DateTime }) - { - // Usually, if a default constant value fails to convert to the parameter type, we want an error at the call site. - // For legacy reasons, decimal and DateTime constants are special. If such a constant fails to convert to the parameter type - // then we want to silently replace it with default(ParameterType). - defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; - } - else - { - if (!conversion.IsValid) - { - GenerateImplicitConversionError(diagnostics, syntax, conversion, defaultValue, parameterType); - } - defaultValue = CreateConversion(defaultValue, conversion, parameterType, diagnostics); - } - - return defaultValue; - } - private BoundExpression GetDefaultParameterSpecialNoConversion(SyntaxNode syntax, ParameterSymbol parameter, DiagnosticBag diagnostics) { var parameterType = parameter.Type; @@ -1349,7 +1265,8 @@ internal void BindDefaultArguments( out BitVector defaultArguments, bool expanded, bool enableCallerInfo, - DiagnosticBag diagnostics) + DiagnosticBag diagnostics, + bool assertMissingParametersAreOptional = true) { var visitedParameters = BitVector.Create(parameters.Length); @@ -1362,8 +1279,8 @@ internal void BindDefaultArguments( } } - // only proceed with binding default arguments if we know there is some optional parameter that has not been matched by an explicit argument - if (!parameters.Any(static (param, visitedParameters) => !visitedParameters[param.Ordinal] && param.IsOptional, visitedParameters)) + // only proceed with binding default arguments if we know there is some parameter that has not been matched by an explicit argument + if (parameters.All(static (param, visitedParameters) => visitedParameters[param.Ordinal], visitedParameters)) { defaultArguments = default; return; @@ -1389,10 +1306,18 @@ internal void BindDefaultArguments( for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; - if (!visitedParameters[parameter.Ordinal] && parameter.IsOptional) + if (!visitedParameters[parameter.Ordinal]) { + // Params array is filled in the local rewriter + if (expanded && parameter.Ordinal == parameters.Length - 1) + { + break; + } + + Debug.Assert(parameter.IsOptional || !assertMissingParametersAreOptional); + defaultArguments[argumentsBuilder.Count] = true; - argumentsBuilder.Add(BindDefaultArgument(node, parameter, containingMember, enableCallerInfo, diagnostics)); + argumentsBuilder.Add(bindDefaultArgument(node, parameter, containingMember, enableCallerInfo, diagnostics)); if (argumentRefKindsBuilder is { Count: > 0 }) { @@ -1410,6 +1335,89 @@ internal void BindDefaultArguments( argsToParamsOpt = argsToParamsBuilder.ToImmutableOrNull(); argsToParamsBuilder.Free(); } + + BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol containingMember, bool enableCallerInfo, DiagnosticBag diagnostics) + { + TypeSymbol parameterType = parameter.Type; + if (Flags.Includes(BinderFlags.ParameterDefaultValue)) + { + // This is only expected to occur in recursive error scenarios, for example: `object F(object param = F()) { }` + // We return a non-error expression here to ensure ERR_DefaultValueMustBeConstant (or another appropriate diagnostics) is produced by the caller. + return new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; + } + + var defaultConstantValue = parameter.ExplicitDefaultConstantValue switch + { + // Bad default values are implicitly replaced with default(T) at call sites. + { IsBad: true } => ConstantValue.Null, + var constantValue => constantValue + }; + Debug.Assert((object?)defaultConstantValue != ConstantValue.Unset); + + var callerSourceLocation = enableCallerInfo ? GetCallerLocation(syntax) : null; + BoundExpression defaultValue; + if (callerSourceLocation is object && parameter.IsCallerLineNumber) + { + int line = callerSourceLocation.SourceTree.GetDisplayLineNumber(callerSourceLocation.SourceSpan); + defaultValue = new BoundLiteral(syntax, ConstantValue.Create(line), Compilation.GetSpecialType(SpecialType.System_Int32)) { WasCompilerGenerated = true }; + } + else if (callerSourceLocation is object && parameter.IsCallerFilePath) + { + string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, Compilation.Options.SourceReferenceResolver); + defaultValue = new BoundLiteral(syntax, ConstantValue.Create(path), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; + } + else if (callerSourceLocation is object && parameter.IsCallerMemberName) + { + var memberName = containingMember.GetMemberCallerName(); + defaultValue = new BoundLiteral(syntax, ConstantValue.Create(memberName), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; + } + else if (defaultConstantValue == ConstantValue.NotAvailable) + { + // There is no constant value given for the parameter in source/metadata. + if (parameterType.IsDynamic() || parameterType.SpecialType == SpecialType.System_Object) + { + // We have something like M([Optional] object x). We have special handling for such situations. + defaultValue = GetDefaultParameterSpecialNoConversion(syntax, parameter, diagnostics); + } + else + { + // The argument to M([Optional] int x) becomes default(int) + defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; + } + } + else if (defaultConstantValue.IsNull) + { + defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; + } + else + { + TypeSymbol constantType = Compilation.GetSpecialType(defaultConstantValue.SpecialType); + defaultValue = new BoundLiteral(syntax, defaultConstantValue, constantType) { WasCompilerGenerated = true }; + } + + HashSet? useSiteDiagnostics = null; + Conversion conversion = Conversions.ClassifyConversionFromExpression(defaultValue, parameterType, ref useSiteDiagnostics); + diagnostics.Add(syntax, useSiteDiagnostics); + + if (!conversion.IsValid && defaultConstantValue is { SpecialType: SpecialType.System_Decimal or SpecialType.System_DateTime }) + { + // Usually, if a default constant value fails to convert to the parameter type, we want an error at the call site. + // For legacy reasons, decimal and DateTime constants are special. If such a constant fails to convert to the parameter type + // then we want to silently replace it with default(ParameterType). + defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; + } + else + { + if (!conversion.IsValid) + { + GenerateImplicitConversionError(diagnostics, syntax, conversion, defaultValue, parameterType); + } + defaultValue = CreateConversion(defaultValue, conversion, parameterType, diagnostics); + } + + return defaultValue; + } + } #nullable disable diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 0ab9edff456aa..d07b5a09806fe 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -762,8 +762,9 @@ private EnumeratorResult GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder bui return EnumeratorResult.FailedAndReported; } - if (SatisfiesIEnumerableInterfaces(ref builder, ref collectionExpr, isAsync, diagnostics, unwrappedCollectionExpr, unwrappedCollectionExprType) is not EnumeratorResult.FailedNotReported and var result) + if (SatisfiesIEnumerableInterfaces(ref builder, unwrappedCollectionExpr, isAsync, diagnostics, unwrappedCollectionExprType) is not EnumeratorResult.FailedNotReported and var result) { + collectionExpr = unwrappedCollectionExpr; return result; } @@ -815,14 +816,13 @@ EnumeratorResult createPatternBasedEnumeratorResult(ref ForEachEnumeratorInfo.Bu } } - private EnumeratorResult SatisfiesIEnumerableInterfaces(ref ForEachEnumeratorInfo.Builder builder, ref BoundExpression collectionExpr, bool isAsync, DiagnosticBag diagnostics, BoundExpression unwrappedCollectionExpr, TypeSymbol unwrappedCollectionExprType) + 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; } - collectionExpr = unwrappedCollectionExpr; if (ReportConstantNullCollectionExpr(collectionExpr, diagnostics)) { return EnumeratorResult.FailedAndReported; @@ -856,14 +856,16 @@ private EnumeratorResult SatisfiesIEnumerableInterfaces(ref ForEachEnumeratorInf 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.ParameterCount == 1); - if (getEnumeratorMethod?.Parameters[0].IsOptional == false) - { - collectionExpr = unwrappedCollectionExpr; - // 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; - } + Debug.Assert(getEnumeratorMethod is null or { ParameterCount: 1 }); + + // C# 8 shipped allowing the CancellationToken of `IAsyncEnumerable.GetAsyncEnumerator` to be non-optional. + // https://github.com/dotnet/roslyn/issues/50182 tracks enabling this error and breaking the scenario. + // 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 { @@ -878,7 +880,16 @@ private EnumeratorResult SatisfiesIEnumerableInterfaces(ref ForEachEnumeratorInf 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); + 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) @@ -1641,7 +1652,7 @@ private MethodArgumentInfo GetParameterlessSpecialTypeMemberInfo(SpecialMember m } /// If method is an extension method, this must be non-null. - private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpression extensionReceiverOpt, bool expanded, SyntaxNode syntax, DiagnosticBag diagnostics) + private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpression extensionReceiverOpt, bool expanded, SyntaxNode syntax, DiagnosticBag diagnostics, bool assertMissingParametersAreOptional = true) { Debug.Assert((extensionReceiverOpt != null) == method.IsExtensionMethod); @@ -1667,7 +1678,8 @@ private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpres defaultArguments: out BitVector defaultArguments, expanded, enableCallerInfo: true, - diagnostics); + diagnostics, + assertMissingParametersAreOptional); return new MethodArgumentInfo(method, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs index 12ba2f140d845..53baedf362b6a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs @@ -141,7 +141,10 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n // ((C)(x)).GetEnumerator(); OR (x).GetEnumerator(); OR async variants (which fill-in arguments for optional parameters) BoundExpression enumeratorVarInitValue = SynthesizeCall(getEnumeratorInfo, forEachSyntax, receiver, - allowExtensionAndOptionalParameters: isAsync || getEnumeratorInfo.Method.IsExtensionMethod); + allowExtensionAndOptionalParameters: isAsync || getEnumeratorInfo.Method.IsExtensionMethod, + // C# 8 shipped allowing the CancellationToken of `IAsyncEnumerable.GetAsyncEnumerator` to be non-optional. + // https://github.com/dotnet/roslyn/issues/50182 tracks making this an error and breaking the scenario. + assertParametersAreOptional: false); // E e = ((C)(x)).GetEnumerator(); BoundStatement enumeratorVarDecl = MakeLocalDeclaration(forEachSyntax, enumeratorVar, enumeratorVarInitValue); @@ -494,12 +497,12 @@ private BoundExpression ConvertReceiverForInvocation(CSharpSyntaxNode syntax, Bo return receiver; } - private BoundExpression SynthesizeCall(MethodArgumentInfo methodArgumentInfo, CSharpSyntaxNode syntax, BoundExpression? receiver, bool allowExtensionAndOptionalParameters) + private BoundExpression SynthesizeCall(MethodArgumentInfo methodArgumentInfo, CSharpSyntaxNode syntax, BoundExpression? receiver, bool allowExtensionAndOptionalParameters, bool assertParametersAreOptional = true) { if (allowExtensionAndOptionalParameters) { // Generate a call with zero explicit arguments, but with implicit arguments for optional and params parameters. - return MakeCallWithNoExplicitArgument(methodArgumentInfo, syntax, receiver); + return MakeCallWithNoExplicitArgument(methodArgumentInfo, syntax, receiver, assertParametersAreOptional); } // Generate a call with literally zero arguments diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index d377b5f67f3b5..eabf52d56c959 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -473,7 +473,7 @@ private BoundExpression GenerateDisposeCall( /// /// Synthesize a call `expression.Method()`, but with some extra smarts to handle extension methods, and to fill-in optional and params parameters. /// - private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo methodArgumentInfo, SyntaxNode syntax, BoundExpression? expression) + private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo methodArgumentInfo, SyntaxNode syntax, BoundExpression? expression, bool assertParametersAreOptional = true) { MethodSymbol method = methodArgumentInfo.Method; @@ -481,12 +481,12 @@ private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo method if (method.IsExtensionMethod) { Debug.Assert(expression == null); - Debug.Assert(method.Parameters.AsSpan()[1..].All(p => (p.IsOptional || p.IsParams) && p.RefKind == RefKind.None)); + Debug.Assert(method.Parameters.AsSpan()[1..].All(assertParametersAreOptional, (p, assertOptional) => (p.IsOptional || p.IsParams || !assertOptional) && p.RefKind == RefKind.None)); Debug.Assert(method.ParameterRefKinds.IsDefaultOrEmpty || method.ParameterRefKinds[0] is RefKind.In or RefKind.None); } else { - Debug.Assert(method.Parameters.All(p => p.IsOptional || p.IsParams)); + Debug.Assert(!assertParametersAreOptional || method.Parameters.All(p => p.IsOptional || p.IsParams)); Debug.Assert(method.ParameterRefKinds.IsDefaultOrEmpty); } #endif diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs index f0baca0c856b1..24f2dd8b34972 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs @@ -543,6 +543,101 @@ public int Current ); } + [Fact] + public void TestWithCurrent_MissingGetterOnInterface() + { + string source = @" +using System.Collections.Generic; +using System.Threading.Tasks; +class C : IAsyncEnumerable +{ + public static async Task M(C c) + { + await foreach (var i in c) + { + } + } + public IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default) => throw null; +} +namespace System.Collections.Generic +{ + public interface IAsyncEnumerable + { + IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default); + } + + public interface IAsyncEnumerator : System.IAsyncDisposable + { + System.Threading.Tasks.Task MoveNextAsync(); + T Current { set; } + } +} +namespace System +{ + public interface IAsyncDisposable + { + System.Threading.Tasks.ValueTask DisposeAsync(); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(source); + comp.VerifyEmitDiagnostics( + // (8,33): error CS8412: Asynchronous foreach requires that the return type 'IAsyncEnumerator' of 'C.GetAsyncEnumerator(CancellationToken)' must have a suitable public 'MoveNextAsync' method and public 'Current' property + // await foreach (var i in c) + Diagnostic(ErrorCode.ERR_BadGetAsyncEnumerator, "c").WithArguments("System.Collections.Generic.IAsyncEnumerator", "C.GetAsyncEnumerator(System.Threading.CancellationToken)").WithLocation(8, 33), + // (24,9): error CS1961: Invalid variance: The type parameter 'T' must be contravariantly valid on 'IAsyncEnumerator.Current'. 'T' is covariant. + // T Current { set; } + Diagnostic(ErrorCode.ERR_UnexpectedVariance, "T").WithArguments("System.Collections.Generic.IAsyncEnumerator.Current", "T", "covariant", "contravariantly").WithLocation(24, 9) + ); + } + + [Fact] + public void TestWithCurrent_MissingPropertyOnInterface() + { + string source = @" +using System.Collections.Generic; +using System.Threading.Tasks; +class C : IAsyncEnumerable +{ + public static async Task M(C c) + { + await foreach (var i in c) + { + } + } + public IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default) => throw null; +} +namespace System.Collections.Generic +{ + public interface IAsyncEnumerable + { + IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default); + } + + public interface IAsyncEnumerator : System.IAsyncDisposable + { + System.Threading.Tasks.Task MoveNextAsync(); + } +} +namespace System +{ + public interface IAsyncDisposable + { + System.Threading.Tasks.ValueTask DisposeAsync(); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(source); + comp.VerifyEmitDiagnostics( + // (8,33): error CS0117: 'IAsyncEnumerator' does not contain a definition for 'Current' + // await foreach (var i in c) + Diagnostic(ErrorCode.ERR_NoSuchMember, "c").WithArguments("System.Collections.Generic.IAsyncEnumerator", "Current").WithLocation(8, 33), + // (8,33): error CS8412: Asynchronous foreach requires that the return type 'IAsyncEnumerator' of 'C.GetAsyncEnumerator(CancellationToken)' must have a suitable public 'MoveNextAsync' method and public 'Current' property + // await foreach (var i in c) + Diagnostic(ErrorCode.ERR_BadGetAsyncEnumerator, "c").WithArguments("System.Collections.Generic.IAsyncEnumerator", "C.GetAsyncEnumerator(System.Threading.CancellationToken)").WithLocation(8, 33) + ); + } + [Fact] public void TestMoveNextAsync_ReturnsTask() { @@ -685,6 +780,53 @@ public int Current CompileAndVerify(comp, expectedOutput: "MoveNextAsync"); } + [Fact] + public void TestMoveNextAsync_Missing() + { + string source = @" +using System.Collections.Generic; +using System.Threading.Tasks; +class C : IAsyncEnumerable +{ + public static async Task M(C c) + { + await foreach (var i in c) + { + } + } + public IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default) => throw null; +} +namespace System.Collections.Generic +{ + public interface IAsyncEnumerable + { + IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default); + } + + public interface IAsyncEnumerator : System.IAsyncDisposable + { + T Current { get; } + } +} +namespace System +{ + public interface IAsyncDisposable + { + System.Threading.Tasks.ValueTask DisposeAsync(); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(source); + comp.VerifyEmitDiagnostics( + // (8,33): error CS0117: 'IAsyncEnumerator' does not contain a definition for 'MoveNextAsync' + // await foreach (var i in c) + Diagnostic(ErrorCode.ERR_NoSuchMember, "c").WithArguments("System.Collections.Generic.IAsyncEnumerator", "MoveNextAsync").WithLocation(8, 33), + // (8,33): error CS8412: Asynchronous foreach requires that the return type 'IAsyncEnumerator' of 'C.GetAsyncEnumerator(CancellationToken)' must have a suitable public 'MoveNextAsync' method and public 'Current' property + // await foreach (var i in c) + Diagnostic(ErrorCode.ERR_BadGetAsyncEnumerator, "c").WithArguments("System.Collections.Generic.IAsyncEnumerator", "C.GetAsyncEnumerator(System.Threading.CancellationToken)").WithLocation(8, 33) + ); + } + [Fact] public void TestWithNonConvertibleElementType() { @@ -4238,6 +4380,7 @@ public IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationTok } [Fact] + [WorkItem(50182, "https://github.com/dotnet/roslyn/issues/50182")] public void GetAsyncEnumerator_CancellationTokenMustBeOptional_OnIAsyncEnumerable() { string source = @" @@ -4274,7 +4417,7 @@ public interface IAsyncDisposable } "; var comp = CreateCompilationWithTasksExtensions(source); - comp.VerifyDiagnostics( + comp.VerifyEmitDiagnostics( // (8,33): error CS8411: Asynchronous foreach statement cannot operate on variables of type 'IAsyncEnumerable' because 'IAsyncEnumerable' does not contain a suitable public instance or extension definition for 'GetAsyncEnumerator' // await foreach (var i in e) Diagnostic(ErrorCode.ERR_AwaitForEachMissingMember, "e").WithArguments("System.Collections.Generic.IAsyncEnumerable", "GetAsyncEnumerator").WithLocation(8, 33) @@ -4282,6 +4425,54 @@ public interface IAsyncDisposable } [Fact] + [WorkItem(50182, "https://github.com/dotnet/roslyn/issues/50182")] + public void GetAsyncEnumerator_CancellationTokenMustBeOptional_OnIAsyncEnumerable_ImplicitImplementation() + { + string source = @" +using System.Collections.Generic; +using System.Threading.Tasks; +class C : IAsyncEnumerable +{ + public static async Task M(C c) + { + await foreach (var i in c) + { + } + } + public IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token) => throw null; +} +namespace System.Collections.Generic +{ + public interface IAsyncEnumerable + { + IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token); + } + + public interface IAsyncEnumerator : System.IAsyncDisposable + { + System.Threading.Tasks.ValueTask MoveNextAsync(); + T Current { get; } + } +} +namespace System +{ + public interface IAsyncDisposable + { + System.Threading.Tasks.ValueTask DisposeAsync(); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(source); + comp.VerifyEmitDiagnostics( + // https://github.com/dotnet/roslyn/issues/50182 tracks erroring here + // (8,33): error CS8411: Asynchronous foreach statement cannot operate on variables of type 'C' because 'C' does not contain a suitable public instance or extension definition for 'GetAsyncEnumerator' + // await foreach (var i in c) + //Diagnostic(ErrorCode.ERR_AwaitForEachMissingMember, "c").WithArguments("C", "GetAsyncEnumerator").WithLocation(8, 33) + ); + } + + [Fact] + [WorkItem(50182, "https://github.com/dotnet/roslyn/issues/50182")] public void GetAsyncEnumerator_CancellationTokenMustBeOptional_OnIAsyncEnumerable_ExplicitImplementation() { string source = @" @@ -4319,10 +4510,54 @@ public interface IAsyncDisposable } "; var comp = CreateCompilationWithTasksExtensions(source); - comp.VerifyDiagnostics( + comp.VerifyEmitDiagnostics( + // https://github.com/dotnet/roslyn/issues/50182 tracks erroring here // (8,33): error CS8411: Asynchronous foreach statement cannot operate on variables of type 'C' because 'C' does not contain a suitable public instance or extension definition for 'GetAsyncEnumerator' // await foreach (var i in c) - Diagnostic(ErrorCode.ERR_AwaitForEachMissingMember, "c").WithArguments("C", "GetAsyncEnumerator").WithLocation(8, 33) + //Diagnostic(ErrorCode.ERR_AwaitForEachMissingMember, "c").WithArguments("C", "GetAsyncEnumerator").WithLocation(8, 33) + ); + } + + [Fact] + public void GetAsyncEnumerator_Missing() + { + string source = @" +using System.Collections.Generic; +using System.Threading.Tasks; +class C : IAsyncEnumerable +{ + public static async Task M(C c) + { + await foreach (var i in c) + { + } + } +} +namespace System.Collections.Generic +{ + public interface IAsyncEnumerable + { + } + + public interface IAsyncEnumerator : System.IAsyncDisposable + { + System.Threading.Tasks.ValueTask MoveNextAsync(); + T Current { get; } + } +} +namespace System +{ + public interface IAsyncDisposable + { + System.Threading.Tasks.ValueTask DisposeAsync(); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(source); + comp.VerifyEmitDiagnostics( + // (8,33): error CS0656: Missing compiler required member 'System.Collections.Generic.IAsyncEnumerable`1.GetAsyncEnumerator' + // await foreach (var i in c) + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Collections.Generic.IAsyncEnumerable`1", "GetAsyncEnumerator").WithLocation(8, 33) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 9894c535b2eaa..215f66b98a2e7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -13099,10 +13099,9 @@ static void M(I i) compilation2.VerifyOperationTree(node, expectedOperationTree: @" IInvalidOperation (OperationKind.Invalid, Type: System.Object, IsInvalid) (Syntax: 'i.R[1]') - Children(3): + Children(2): IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: I, IsInvalid) (Syntax: 'i') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3, IsInvalid, IsImplicit) (Syntax: 'i.R[1]') "); } diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index 164975bf526c5..9392d7ad68434 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -374,6 +374,22 @@ public static bool Any(this ImmutableArray array, Func(this ImmutableArray array, Func predicate, TArg arg) + { + int n = array.Length; + for (int i = 0; i < n; i++) + { + var a = array[i]; + + if (!predicate(a, arg)) + { + return false; + } + } + + return true; + } + public static async Task AnyAsync(this ImmutableArray array, Func> predicateAsync) { int n = array.Length; diff --git a/src/Compilers/Core/Portable/InternalUtilities/SpanUtilities.cs b/src/Compilers/Core/Portable/InternalUtilities/SpanUtilities.cs index c1ccc90aee135..98b512186e329 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/SpanUtilities.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/SpanUtilities.cs @@ -2,15 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace System +using System; + +namespace Microsoft.CodeAnalysis { - internal static class SpanExtensions + internal static class SpanUtilities { - public static bool All(this ReadOnlySpan span, Func predicate) + public static bool All(this ReadOnlySpan span, TParam param, Func predicate) { foreach (var e in span) { - if (!predicate(e)) + if (!predicate(e, param)) { return false; }