diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index 3306d7e5897bb2740c83134207b246ce74a1c9ff..c91f5cceb9d84925633ef3aff38b23aa47952b6c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -18,8 +18,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bool used) { - var right = node.Right; - var loweredRight = VisitExpression(right); + var loweredRight = VisitExpression(node.Right); BoundExpression left = node.Left; BoundExpression loweredLeft; @@ -39,7 +38,7 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo if (eventAccess.EventSymbol.IsWindowsRuntimeEvent) { Debug.Assert(node.RefKind == RefKind.None); - return VisitWindowsRuntimeEventFieldAssignmentOperator(node.Syntax, eventAccess, right); + return VisitWindowsRuntimeEventFieldAssignmentOperator(node.Syntax, eventAccess, loweredRight); } goto default; } @@ -105,6 +104,26 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo isCompoundAssignment, isChecked).ToExpression(); + case BoundKind.EventAccess: + var eventAccess = (BoundEventAccess)rewrittenLeft; + Debug.Assert(eventAccess.IsUsableAsField); + if (eventAccess.EventSymbol.IsWindowsRuntimeEvent) + { + const bool isDynamic = false; + return RewriteWindowsRuntimeEventAssignmentOperator(eventAccess.Syntax, + eventAccess.EventSymbol, + EventAssignmentKind.Assignment, + isDynamic, + eventAccess.ReceiverOpt, + rewrittenRight); + } + + // Only Windows Runtime field-like events can come through here: + // - Assignment operation is not supported for custom (non-field like) events. + // - Access to regular field-like events is expected to be lowered to at least a field access + // when we reach here. + throw ExceptionUtilities.Unreachable; + default: return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, RefKind.None, type, used); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index 2b70232884863cc3f9bd0fde02efb74261382467..1e8f69755e997664d935224b5788bb8c5c310e74 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -120,8 +120,10 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO return result; } - private BoundPropertyAccess TransformPropertyAccess(BoundPropertyAccess prop, ArrayBuilder stores, ArrayBuilder temps) + private BoundExpression TransformPropertyOrEventReceiver(Symbol propertyOrEvent, BoundExpression receiverOpt, ArrayBuilder stores, ArrayBuilder temps) { + Debug.Assert(propertyOrEvent.Kind == SymbolKind.Property || propertyOrEvent.Kind == SymbolKind.Event); + // We need to stash away the receiver so that it does not get evaluated twice. // If the receiver is classified as a value of reference type then we can simply say // @@ -141,14 +143,14 @@ private BoundPropertyAccess TransformPropertyAccess(BoundPropertyAccess prop, Ar // assume that was the case. // If the property is static or if the receiver is of kind "Base" or "this", then we can just generate prop = prop + value - if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !CanChangeValueBetweenReads(prop.ReceiverOpt)) + if (receiverOpt == null || propertyOrEvent.IsStatic || !CanChangeValueBetweenReads(receiverOpt)) { - return prop; + return receiverOpt; } - Debug.Assert(prop.ReceiverOpt.Kind != BoundKind.TypeExpression); + Debug.Assert(receiverOpt.Kind != BoundKind.TypeExpression); - BoundExpression rewrittenReceiver = VisitExpression(prop.ReceiverOpt); + BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); BoundAssignmentOperator assignmentToTemp; @@ -165,9 +167,7 @@ private BoundPropertyAccess TransformPropertyAccess(BoundPropertyAccess prop, Ar stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); - // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. - // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of properties. - return new BoundPropertyAccess(prop.Syntax, receiverTemp, prop.PropertySymbol, prop.ResultKind, prop.Type); + return receiverTemp; } private BoundDynamicMemberAccess TransformDynamicMemberAccess(BoundDynamicMemberAccess memberAccess, ArrayBuilder stores, ArrayBuilder temps) @@ -318,8 +318,7 @@ private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAcce argumentRefKinds = GetRefKindsOrNull(refKinds); refKinds.Free(); - // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. - // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of indexers. + // This is a temporary object that will be rewritten away before the lowering completes. return new BoundIndexerAccess( syntax, transformedReceiver, @@ -332,15 +331,31 @@ private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAcce indexerAccess.Type); } - private BoundFieldAccess TransformReferenceTypeFieldAccess(BoundFieldAccess fieldAccess, BoundExpression receiver, ArrayBuilder stores, ArrayBuilder temps) + /// + /// Returns true if the was lowered and transformed. + /// The is not changed if this function returns false. + /// + private bool TransformCompoundAssignmentFieldOrEventAccessReceiver(Symbol fieldOrEvent, ref BoundExpression receiver, ArrayBuilder stores, ArrayBuilder temps) { + Debug.Assert(fieldOrEvent.Kind == SymbolKind.Field || fieldOrEvent.Kind == SymbolKind.Event); + + //If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value + if (fieldOrEvent.IsStatic || !CanChangeValueBetweenReads(receiver)) + { + return true; + } + else if (!receiver.Type.IsReferenceType) + { + return false; + } + Debug.Assert(receiver.Type.IsReferenceType); Debug.Assert(receiver.Kind != BoundKind.TypeExpression); BoundExpression rewrittenReceiver = VisitExpression(receiver); if (rewrittenReceiver.Type.IsTypeParameter()) { - var memberContainingType = fieldAccess.FieldSymbol.ContainingType; + var memberContainingType = fieldOrEvent.ContainingType; // From the verifier prospective type parameters do not contain fields or methods. // the instance must be "boxed" to access the field @@ -352,7 +367,8 @@ private BoundFieldAccess TransformReferenceTypeFieldAccess(BoundFieldAccess fiel var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); - return new BoundFieldAccess(fieldAccess.Syntax, receiverTemp, fieldAccess.FieldSymbol, null); + receiver = receiverTemp; + return true; } private BoundDynamicIndexerAccess TransformDynamicIndexerAccess(BoundDynamicIndexerAccess indexerAccess, ArrayBuilder stores, ArrayBuilder temps) @@ -452,7 +468,9 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL var propertyAccess = (BoundPropertyAccess)originalLHS; if (propertyAccess.PropertySymbol.RefKind == RefKind.None) { - return TransformPropertyAccess(propertyAccess, stores, temps); + // This is a temporary object that will be rewritten away before the lowering completes. + return propertyAccess.Update(TransformPropertyOrEventReceiver(propertyAccess.PropertySymbol, propertyAccess.ReceiverOpt, stores, temps), + propertyAccess.PropertySymbol, propertyAccess.ResultKind, propertyAccess.Type); } } break; @@ -479,14 +497,9 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL var fieldAccess = (BoundFieldAccess)originalLHS; BoundExpression receiverOpt = fieldAccess.ReceiverOpt; - //If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value - if (fieldAccess.FieldSymbol.IsStatic || !CanChangeValueBetweenReads(receiverOpt)) - { - return fieldAccess; - } - else if (receiverOpt.Type.IsReferenceType) + if (TransformCompoundAssignmentFieldOrEventAccessReceiver(fieldAccess.FieldSymbol, ref receiverOpt, stores, temps)) { - return TransformReferenceTypeFieldAccess(fieldAccess, receiverOpt, stores, temps); + return MakeFieldAccess(fieldAccess.Syntax, receiverOpt, fieldAccess.FieldSymbol, fieldAccess.ConstantValueOpt, fieldAccess.ResultKind, fieldAccess.Type, fieldAccess); } } break; @@ -544,6 +557,26 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL case BoundKind.RefValueOperator: break; + case BoundKind.EventAccess: + { + var eventAccess = (BoundEventAccess)originalLHS; + Debug.Assert(eventAccess.IsUsableAsField); + BoundExpression receiverOpt = eventAccess.ReceiverOpt; + + if (eventAccess.EventSymbol.IsWindowsRuntimeEvent) + { + // This is a temporary object that will be rewritten away before the lowering completes. + return eventAccess.Update(TransformPropertyOrEventReceiver(eventAccess.EventSymbol, eventAccess.ReceiverOpt, stores, temps), + eventAccess.EventSymbol, eventAccess.IsUsableAsField, eventAccess.ResultKind, eventAccess.Type); + } + + if (TransformCompoundAssignmentFieldOrEventAccessReceiver(eventAccess.EventSymbol, ref receiverOpt, stores, temps)) + { + return MakeEventAccess(eventAccess.Syntax, receiverOpt, eventAccess.EventSymbol, eventAccess.ConstantValue, eventAccess.ResultKind, eventAccess.Type); + } + } + break; + default: throw ExceptionUtilities.UnexpectedValue(originalLHS.Kind); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs index 3d4f00ff38b22a0224dc140dc1d1b27794bae446..24de0949a5fcd20d7bfc2fb04138cb0b0c71e21a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs @@ -172,7 +172,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode return new BoundSequence(syntax, tempSymbols, sideEffects.ToImmutableAndFree(), marshalCall, marshalCall.Type); } - private BoundExpression VisitWindowsRuntimeEventFieldAssignmentOperator(SyntaxNode syntax, BoundEventAccess left, BoundExpression right) + private BoundExpression VisitWindowsRuntimeEventFieldAssignmentOperator(SyntaxNode syntax, BoundEventAccess left, BoundExpression rewrittenRight) { Debug.Assert(left.IsUsableAsField); @@ -181,7 +181,6 @@ private BoundExpression VisitWindowsRuntimeEventFieldAssignmentOperator(SyntaxNo Debug.Assert(eventSymbol.IsWindowsRuntimeEvent); BoundExpression rewrittenReceiverOpt = left.ReceiverOpt == null ? null : VisitExpression(left.ReceiverOpt); - BoundExpression rewrittenRight = VisitExpression(right); const bool isDynamic = false; return RewriteWindowsRuntimeEventAssignmentOperator( diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index 66963b6ea701a277e8a144bb94077febecb771e1..bcb238fa028723e72c8da2d2457abb42e1dae9d0 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -6159,7 +6159,235 @@ public static void Main() // (9,18): error CS8186: A foreach loop must declare its iteration variables. // foreach ((arr[out int size], int b) in data) {} Diagnostic(ErrorCode.ERR_MustDeclareForeachIteration, "(arr[out int size], int b)").WithLocation(9, 18) + ); + } + + [Fact] + [WorkItem(16962, "https://github.com/dotnet/roslyn/issues/16962")] + public void Events_01() + { + string source = @" +class C +{ + static event System.Action E; + + static void Main() + { + (E, _) = (null, 1); + System.Console.WriteLine(E == null); + (E, _) = (Handler, 1); + E(); + } + + static void Handler() + { + System.Console.WriteLine(""Handler""); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: +@"True +Handler", additionalRefs: s_valueTupleRefs); + comp.VerifyDiagnostics(); + } + + [Fact] + [WorkItem(16962, "https://github.com/dotnet/roslyn/issues/16962")] + public void Events_02() + { + string source = @" +struct S +{ + event System.Action E; + + class C + { + static void Main() + { + var s = new S(); + (s.E, _) = (null, 1); + System.Console.WriteLine(s.E == null); + (s.E, _) = (Handler, 1); + s.E(); + } + + static void Handler() + { + System.Console.WriteLine(""Handler""); + } + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: +@"True +Handler", additionalRefs: s_valueTupleRefs); + comp.VerifyDiagnostics(); + } + + [Fact] + [WorkItem(16962, "https://github.com/dotnet/roslyn/issues/16962")] + public void Events_03() + { + string source1 = @" +public interface EventInterface +{ + event System.Action E; +} +"; + + var comp1 = CreateCompilation(source1, WinRtRefs, TestOptions.ReleaseWinMD, TestOptions.Regular); + + string source2 = @" +class C : EventInterface +{ + public event System.Action E; + + static void Main() + { + var c = new C(); + c.Test(); + } + + void Test() + { + (E, _) = (null, 1); + System.Console.WriteLine(E == null); + (E, _) = (Handler, 1); + E(); + } + + static void Handler() + { + System.Console.WriteLine(""Handler""); + } +} +"; + + var comp2 = CompileAndVerify(source2, expectedOutput: +@"True +Handler", additionalRefs: WinRtRefs.Concat(new[] { SystemRuntimeFacadeRef, ValueTupleRef, comp1.ToMetadataReference() })); + comp2.VerifyDiagnostics(); + + Assert.True(comp2.Compilation.GetMember("C.E").IsWindowsRuntimeEvent); + } + [Fact] + [WorkItem(16962, "https://github.com/dotnet/roslyn/issues/16962")] + public void Events_04() + { + string source1 = @" +public interface EventInterface +{ + event System.Action E; +} +"; + + var comp1 = CreateCompilation(source1, WinRtRefs, TestOptions.ReleaseWinMD, TestOptions.Regular); + + string source2 = @" +struct S : EventInterface +{ + public event System.Action E; + + class C + { + S s = new S(); + + static void Main() + { + var c = new C(); + (GetC(c).s.E, _) = (null, GetInt(1)); + System.Console.WriteLine(c.s.E == null); + (GetC(c).s.E, _) = (Handler, GetInt(2)); + c.s.E(); + } + + static int GetInt(int i) + { + System.Console.WriteLine(i); + return i; + } + + static C GetC(C c) + { + System.Console.WriteLine(""GetC""); + return c; + } + + static void Handler() + { + System.Console.WriteLine(""Handler""); + } + } +} +"; + + var comp2 = CompileAndVerify(source2, expectedOutput: +@"GetC +1 +True +GetC +2 +Handler", additionalRefs: WinRtRefs.Concat(new[] { SystemRuntimeFacadeRef, ValueTupleRef, comp1.ToMetadataReference() })); + comp2.VerifyDiagnostics(); + + Assert.True(comp2.Compilation.GetMember("S.E").IsWindowsRuntimeEvent); + } + + [Fact] + [WorkItem(16962, "https://github.com/dotnet/roslyn/issues/16962")] + public void Events_05() + { + string source = @" +class C +{ + public static event System.Action E; +} + +class Program +{ + static void Main() + { + (C.E, _) = (null, 1); + } +} +"; + + var compilation = CreateCompilationWithMscorlib(source, references: s_valueTupleRefs); + compilation.VerifyDiagnostics( + // (11,12): error CS0070: The event 'C.E' can only appear on the left hand side of += or -= (except when used from within the type 'C') + // (C.E, _) = (null, 1); + Diagnostic(ErrorCode.ERR_BadEventUsage, "E").WithArguments("C.E", "C").WithLocation(11, 12) + ); + } + + [Fact] + [WorkItem(16962, "https://github.com/dotnet/roslyn/issues/16962")] + public void Events_06() + { + string source = @" +class C +{ + static event System.Action E + { + add {} + remove {} + } + + static void Main() + { + (E, _) = (null, 1); + } +} +"; + + var compilation = CreateCompilationWithMscorlib(source, references: s_valueTupleRefs); + compilation.VerifyDiagnostics( + // (12,10): error CS0079: The event 'C.E' can only appear on the left hand side of += or -= + // (E, _) = (null, 1); + Diagnostic(ErrorCode.ERR_BadEventUsageNoField, "E").WithArguments("C.E").WithLocation(12, 10) ); }