Skip to content

Commit 009a161

Browse files
authored
Warn on implicit copies for event assignment within readonly members (#35710)
1 parent 82a709e commit 009a161

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs

+2
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ private BoundExpression BindEventAssignment(AssignmentExpressionSyntax node, Bou
264264
}
265265
else
266266
{
267+
CheckImplicitThisCopyInReadOnlyMember(receiverOpt, method, diagnostics);
268+
267269
if (!this.IsAccessible(method, ref useSiteDiagnostics, this.GetAccessThroughType(receiverOpt)))
268270
{
269271
// CONSIDER: depending on the accessibility (e.g. if it's private), dev10 might just report the whole event bogus.

src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs

+153-2
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,153 @@ public readonly void M()
760760
}
761761

762762
[Fact]
763-
public void ReadOnlyMethod_AddRemoveEventHandlers()
763+
public void ReadOnlyMethod_EventAssignment()
764+
{
765+
var csharp = @"
766+
using System;
767+
768+
public struct S
769+
{
770+
public readonly void M()
771+
{
772+
Console.WriteLine(E is null);
773+
// should create local copy
774+
E += () => {}; // warning
775+
Console.WriteLine(E is null);
776+
E -= () => {}; // warning
777+
778+
// explicit local copy, no warning
779+
var copy = this;
780+
copy.E += () => {};
781+
Console.WriteLine(copy.E is null);
782+
}
783+
784+
public event Action E;
785+
786+
static void Main()
787+
{
788+
var s = new S();
789+
s.M();
790+
}
791+
}
792+
";
793+
794+
var verifier = CompileAndVerify(csharp, expectedOutput:
795+
@"True
796+
True
797+
False");
798+
verifier.VerifyDiagnostics(
799+
// (10,9): warning CS8656: Call to non-readonly member 'S.E.add' from a 'readonly' member results in an implicit copy of 'this'.
800+
// E += () => {}; // warning
801+
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "E").WithArguments("S.E.add", "this").WithLocation(10, 9),
802+
// (12,9): warning CS8656: Call to non-readonly member 'S.E.remove' from a 'readonly' member results in an implicit copy of 'this'.
803+
// E -= () => {}; // warning
804+
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "E").WithArguments("S.E.remove", "this").WithLocation(12, 9));
805+
}
806+
807+
[Fact]
808+
public void ReadOnlyStruct_EventAssignment()
809+
{
810+
var csharp = @"
811+
using System;
812+
813+
public struct S
814+
{
815+
public void M()
816+
{
817+
E += () => {};
818+
E -= () => {};
819+
}
820+
821+
public event Action E { add {} remove {} }
822+
}
823+
";
824+
var comp = CreateCompilation(csharp);
825+
comp.VerifyDiagnostics();
826+
}
827+
828+
[Fact]
829+
public void ReadOnlyMethod_ReadOnlyEventAssignment()
830+
{
831+
var csharp = @"
832+
using System;
833+
834+
public struct S
835+
{
836+
public readonly void M()
837+
{
838+
E += () => {};
839+
E -= () => {};
840+
}
841+
842+
public readonly event Action E { add {} remove {} }
843+
}
844+
";
845+
846+
var verifier = CreateCompilation(csharp);
847+
verifier.VerifyDiagnostics();
848+
}
849+
850+
[Fact]
851+
public void ReadOnlyMethod_Field_EventAssignment()
852+
{
853+
var csharp = @"
854+
#pragma warning disable 0067
855+
using System;
856+
857+
public struct S2
858+
{
859+
public event Action E;
860+
}
861+
862+
public struct S1
863+
{
864+
public S2 s2;
865+
866+
public readonly void M()
867+
{
868+
s2.E += () => {};
869+
s2.E -= () => {};
870+
}
871+
}
872+
";
873+
874+
// TODO: should warn in warning wave https://github.com/dotnet/roslyn/issues/33968
875+
var verifier = CreateCompilation(csharp);
876+
verifier.VerifyDiagnostics();
877+
}
878+
879+
[Fact]
880+
public void ReadOnlyStruct_Field_EventAssignment()
881+
{
882+
var csharp = @"
883+
#pragma warning disable 0067
884+
using System;
885+
886+
public struct S2
887+
{
888+
public event Action E;
889+
}
890+
891+
public readonly struct S1
892+
{
893+
public readonly S2 s2;
894+
895+
public void M()
896+
{
897+
s2.E += () => {};
898+
s2.E -= () => {};
899+
}
900+
}
901+
";
902+
903+
// TODO: should warn in warning wave https://github.com/dotnet/roslyn/issues/33968
904+
var verifier = CreateCompilation(csharp);
905+
verifier.VerifyDiagnostics();
906+
}
907+
908+
[Fact]
909+
public void ReadOnlyMethod_EventAssignment_Error()
764910
{
765911
var csharp = @"
766912
using System;
@@ -779,9 +925,14 @@ void handler(EventArgs args) { }
779925
}
780926
}
781927
";
782-
// should warn about E += handler in warning wave (see https://github.com/dotnet/roslyn/issues/33968)
783928
var comp = CreateCompilation(csharp);
784929
comp.VerifyDiagnostics(
930+
// (9,9): warning CS8656: Call to non-readonly member 'S.E.add' from a 'readonly' member results in an implicit copy of 'this'.
931+
// E += handler;
932+
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "E").WithArguments("S.E.add", "this").WithLocation(9, 9),
933+
// (10,9): warning CS8656: Call to non-readonly member 'S.E.remove' from a 'readonly' member results in an implicit copy of 'this'.
934+
// E -= handler;
935+
Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "E").WithArguments("S.E.remove", "this").WithLocation(10, 9),
785936
// (11,9): error CS1604: Cannot assign to 'E' because it is read-only
786937
// E = handler;
787938
Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "E").WithArguments("E").WithLocation(11, 9));

0 commit comments

Comments
 (0)