diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 741ff594458968a1f52206f6616048b23dd04abe..d5e3d1fea6c775ba88a99147eadc5c36236799d8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -6307,10 +6307,11 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess // access cannot be a pointer if ((!accessType.IsReferenceType && !accessType.IsValueType) || accessType.IsPointerType()) { - // Assume result type of the access is void when result value isn't used and cannot be made nullable. - // We are not doing this for types that can be made nullable to still allow expression evaluator to - // to get the value. - bool resultIsNotUsed = false; + // Result type of the access is void when result value cannot be made nullable. + // For improved diagnostics we detect the cases where the value will be used and produce a + // more specific (though not technically correct) diagnostic here: + // "Error CS0023: Operator '?' cannot be applied to operand of type 'T'" + bool resultIsUsed = true; CSharpSyntaxNode parent = node.Parent; if (parent != null) @@ -6318,37 +6319,35 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess switch (parent.Kind()) { case SyntaxKind.ExpressionStatement: - resultIsNotUsed = ((ExpressionStatementSyntax)parent).Expression == node; + resultIsUsed = ((ExpressionStatementSyntax)parent).Expression != node; break; case SyntaxKind.SimpleLambdaExpression: - resultIsNotUsed = (((SimpleLambdaExpressionSyntax)parent).Body == node) && ContainingMethodOrLambdaReturnsVoid(); + resultIsUsed = (((SimpleLambdaExpressionSyntax)parent).Body != node) || ContainingMethodOrLambdaRequiresValue(); break; case SyntaxKind.ParenthesizedLambdaExpression: - resultIsNotUsed = (((ParenthesizedLambdaExpressionSyntax)parent).Body == node) && ContainingMethodOrLambdaReturnsVoid(); + resultIsUsed = (((ParenthesizedLambdaExpressionSyntax)parent).Body != node) || ContainingMethodOrLambdaRequiresValue(); break; case SyntaxKind.ArrowExpressionClause: - resultIsNotUsed = (((ArrowExpressionClauseSyntax)parent).Expression == node) && ContainingMethodOrLambdaReturnsVoid(); + resultIsUsed = (((ArrowExpressionClauseSyntax)parent).Expression != node) || ContainingMethodOrLambdaRequiresValue(); break; case SyntaxKind.ForStatement: // Incrementors and Initializers doesn't have to produce a value var loop = (ForStatementSyntax)parent; - resultIsNotUsed = loop.Incrementors.Contains(node) || loop.Initializers.Contains(node); + resultIsUsed = !loop.Incrementors.Contains(node) && !loop.Initializers.Contains(node); break; } } - if (resultIsNotUsed) - { - accessType = GetSpecialType(SpecialType.System_Void, diagnostics, node); - } - else + if (resultIsUsed) { return GenerateBadConditionalAccessNodeError(node, receiver, access, diagnostics); } + + accessType = GetSpecialType(SpecialType.System_Void, diagnostics, node); } // if access has value type, the type of the conditional access is nullable of that @@ -6360,10 +6359,13 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess return new BoundConditionalAccess(node, receiver, access, accessType); } - private bool ContainingMethodOrLambdaReturnsVoid() + private bool ContainingMethodOrLambdaRequiresValue() { - Symbol containingMember = ContainingMemberOrLambda; - return (object)containingMember != null && containingMember.Kind == SymbolKind.Method && ((MethodSymbol)containingMember).ReturnsVoid; + var containingMethod = ContainingMemberOrLambda as MethodSymbol; + return + (object)containingMethod == null || + !containingMethod.ReturnsVoid && + !containingMethod.IsTaskReturningAsync(this.Compilation); } private BoundConditionalAccess GenerateBadConditionalAccessNodeError(ConditionalAccessExpressionSyntax node, BoundExpression receiver, BoundExpression access, DiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs index e9d9048df82f46c255b186ef185deaa5f818aaea..f0eeb459faf9cb763b62cd650b0ed7aebc1fbf75 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs @@ -5542,5 +5542,57 @@ public T M() M ---"); } + + [WorkItem(74, "https://github.com/dotnet/roslyn/issues/74")] + [Fact] + public void ConditionalInAsyncTask() + { + var source = @" +#pragma warning disable CS1998 // suppress 'no await in async' warning +using System; +using System.Threading.Tasks; + +class Foo +{ + public T Method(int i) + { + Console.Write(i); + return default(T); // returns value of unconstrained type parameter type + } + public void M1(Foo x) => x?.Method(4); + public async void M2(Foo x) => x?.Method(5); + public async Task M3(Foo x) => x?.Method(6); + public async Task M4() { + Foo a = new Foo(); + Foo b = null; + + Action f1 = async () => a?.Method(1); + f1(); + f1 = async () => b?.Method(0); + f1(); + + Func f2 = async () => a?.Method(2); + await f2(); + Func f3 = async () => b?.Method(3); + await f3(); + + M1(a); M1(b); + M2(a); M2(b); + await M3(a); + await M3(b); + } +} +class Program +{ + public static void Main() + { + // this will complete synchronously as there are no truly async ops. + new Foo().M4(); + } +}"; + var compilation = CreateCompilationWithMscorlib45( + source, references: new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, options: TestOptions.DebugExe); + CompileAndVerify(compilation, expectedOutput: "12456"); + } } }