Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,11 @@ public sealed override bool IsWindowsRuntimeEvent

private bool ComputeIsWindowsRuntimeEvent()
{
if (PartialDefinitionPart is { } partialDefinitionPart)
{
return partialDefinitionPart.IsWindowsRuntimeEvent;
}

// If you explicitly implement an event, then you're a WinRT event if and only if it's a WinRT event.
ImmutableArray<EventSymbol> explicitInterfaceImplementations = this.ExplicitInterfaceImplementations;
if (!explicitInterfaceImplementations.IsEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3982,6 +3982,13 @@ private static void MergePartialMembers(
{
Debug.Assert(symbol.IsPartialMember());

// Accessor symbols and their diagnostics are handled by processing the associated member.
// We cannot add them to the map (signature comparison on partial event accessors can lead to cycles through IsWindowsRuntimeEvent).
if (symbol is SourcePropertyAccessorSymbol or SourceEventAccessorSymbol)
{
continue;
}

if (!membersBySignature.TryGetValue(symbol, out var prev))
{
membersBySignature.Add(symbol, symbol);
Expand All @@ -4007,13 +4014,10 @@ private static void MergePartialMembers(
mergePartialEvents(nonTypeMembers, currentEvent, prevEvent, diagnostics);
break;

case (SourcePropertyAccessorSymbol, SourcePropertyAccessorSymbol):
case (SourceEventAccessorSymbol, SourceEventAccessorSymbol):
break; // accessor symbols and their diagnostics are handled by processing the associated member

default:
// This is an error scenario. We simply don't merge the symbols in this case and a duplicate name diagnostic is reported separately.
// One way this case can be reached is if type contains both `public partial int P { get; }` and `public partial int get_P();`.
Debug.Assert(symbol.Kind != prev.Kind);
Debug.Assert(symbol is SourceOrdinaryMethodSymbol or SourcePropertySymbol or SourcePropertyAccessorSymbol or SourceEventAccessorSymbol);
Debug.Assert(prev is SourceOrdinaryMethodSymbol or SourcePropertySymbol or SourcePropertyAccessorSymbol or SourceEventAccessorSymbol);
break;
Expand All @@ -4024,6 +4028,12 @@ private static void MergePartialMembers(
{
var symbol = (Symbol)pair.Value;
Debug.Assert(symbol.IsPartialMember());

if (symbol is SourcePropertyAccessorSymbol or SourceEventAccessorSymbol)
{
continue;
}

membersBySignature.Add(symbol, symbol);
}

Expand Down Expand Up @@ -4073,10 +4083,6 @@ private static void MergePartialMembers(
}
break;

case SourceEventAccessorSymbol:
case SourcePropertyAccessorSymbol:
break; // diagnostics for missing partial accessors are handled in 'mergePartialProperties'/'mergePartialEvents'.

default:
throw ExceptionUtilities.UnexpectedValue(symbol);
}
Expand Down
180 changes: 180 additions & 0 deletions src/Compilers/CSharp/Test/Emit3/PartialEventsAndConstructorsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,64 @@ partial event Action E { remove { } }
Assert.Null(e2.PartialDefinitionPart);
}

[Fact]
public void DuplicateDeclarations_05()
{
var source = """
partial class C
{
partial event System.Action E;
partial void add_E(System.Action value) { }
partial void remove_E(System.Action value) { }
}
""";
CreateCompilation(source).VerifyDiagnostics(
// (3,33): error CS9275: Partial member 'C.E' must have an implementation part.
// partial event System.Action E;
Diagnostic(ErrorCode.ERR_PartialMemberMissingImplementation, "E").WithArguments("C.E").WithLocation(3, 33),
// (3,33): error CS0082: Type 'C' already reserves a member called 'add_E' with the same parameter types
// partial event System.Action E;
Diagnostic(ErrorCode.ERR_MemberReserved, "E").WithArguments("add_E", "C").WithLocation(3, 33),
// (3,33): error CS0082: Type 'C' already reserves a member called 'remove_E' with the same parameter types
// partial event System.Action E;
Diagnostic(ErrorCode.ERR_MemberReserved, "E").WithArguments("remove_E", "C").WithLocation(3, 33),
// (4,18): error CS0759: No defining declaration found for implementing declaration of partial method 'C.add_E(Action)'
// partial void add_E(System.Action value) { }
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "add_E").WithArguments("C.add_E(System.Action)").WithLocation(4, 18),
// (5,18): error CS0759: No defining declaration found for implementing declaration of partial method 'C.remove_E(Action)'
// partial void remove_E(System.Action value) { }
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "remove_E").WithArguments("C.remove_E(System.Action)").WithLocation(5, 18));
}

[Fact]
public void DuplicateDeclarations_06()
{
var source = """
partial class C
{
partial void add_E(System.Action value) { }
partial void remove_E(System.Action value) { }
partial event System.Action E;
}
""";
CreateCompilation(source).VerifyDiagnostics(
// (3,18): error CS0759: No defining declaration found for implementing declaration of partial method 'C.add_E(Action)'
// partial void add_E(System.Action value) { }
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "add_E").WithArguments("C.add_E(System.Action)").WithLocation(3, 18),
// (4,18): error CS0759: No defining declaration found for implementing declaration of partial method 'C.remove_E(Action)'
// partial void remove_E(System.Action value) { }
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "remove_E").WithArguments("C.remove_E(System.Action)").WithLocation(4, 18),
// (5,33): error CS9275: Partial member 'C.E' must have an implementation part.
// partial event System.Action E;
Diagnostic(ErrorCode.ERR_PartialMemberMissingImplementation, "E").WithArguments("C.E").WithLocation(5, 33),
// (5,33): error CS0082: Type 'C' already reserves a member called 'add_E' with the same parameter types
// partial event System.Action E;
Diagnostic(ErrorCode.ERR_MemberReserved, "E").WithArguments("add_E", "C").WithLocation(5, 33),
// (5,33): error CS0082: Type 'C' already reserves a member called 'remove_E' with the same parameter types
// partial event System.Action E;
Diagnostic(ErrorCode.ERR_MemberReserved, "E").WithArguments("remove_E", "C").WithLocation(5, 33));
}

[Fact]
public void EventInitializer_Single()
{
Expand Down Expand Up @@ -1301,6 +1359,128 @@ public required partial event System.Action E { add { } remove { } }
Diagnostic(ErrorCode.ERR_BadMemberFlag, "E").WithArguments("required").WithLocation(4, 49));
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80986")]
public void InterfaceImplementation(bool first, bool second)
{
if (!first && !second)
{
return;
}

var source = $$"""
using System;

interface I
{
event EventHandler E;
}

partial class C {{(first ? ": I" : "")}}
{
public partial event EventHandler E;
}

partial class C {{(second ? ": I" : "")}}
{
public partial event EventHandler E { add { } remove { } }
}
""";
CompileAndVerify(source,
options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All),
symbolValidator: validate,
sourceSymbolValidator: validate).VerifyDiagnostics();

static void validate(ModuleSymbol module)
{
var e = module.GlobalNamespace.GetMember<EventSymbol>("C.E");
Assert.False(e.IsWindowsRuntimeEvent);

var ie = module.GlobalNamespace.GetMember<EventSymbol>("I.E");
Assert.False(ie.IsWindowsRuntimeEvent);

Assert.Same(e, e.ContainingType.FindImplementationForInterfaceMember(ie));

if (module is SourceModuleSymbol)
{
Assert.True(e.IsPartialDefinition);
Assert.False(((SourceEventSymbol)e).PartialImplementationPart!.IsWindowsRuntimeEvent);
}
}
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80986")]
public void InterfaceImplementation_WinRt(bool first, bool second)
{
if (!first && !second)
{
return;
}

var source1 = """
using System;

public interface I
{
event EventHandler E;
}
""";

var source2 = $$"""
using System;

partial class C {{(first ? ": I" : "")}}
{
public partial event EventHandler E;
}

partial class C {{(second ? ": I" : "")}}
{
public partial event EventHandler E { add { return default; } remove { } }
}
""";

CompileAndVerifyWithWinRt([source1, source2],
options: TestOptions.DebugWinMD.WithMetadataImportOptions(MetadataImportOptions.All),
symbolValidator: static m => validate(m, true),
sourceSymbolValidator: static m => validate(m, true)).VerifyDiagnostics();

// If the interface is WinMD and the class is not WinMD, the event is WinRt
CompileAndVerifyWithWinRt(source2,
references: [
CompileAndVerifyWithWinRt(source1, options: TestOptions.DebugWinMD).VerifyDiagnostics().GetImageReference()
],
options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All),
symbolValidator: static m => validate(m, true),
sourceSymbolValidator: static m => validate(m, true)).VerifyDiagnostics();

// If the interface is not WinMD and the class is WinMD, the event is not WinRt
CompileAndVerifyWithWinRt(source2.Replace("add { return default; }", "add { }"),
references: [
CompileAndVerifyWithWinRt(source1, options: TestOptions.DebugDll).VerifyDiagnostics().GetImageReference()
],
options: TestOptions.DebugWinMD.WithMetadataImportOptions(MetadataImportOptions.All),
symbolValidator: static m => validate(m, false),
sourceSymbolValidator: static m => validate(m, false)).VerifyDiagnostics();

static void validate(ModuleSymbol module, bool isWinRtEvent)
{
var e = module.GlobalNamespace.GetMember<EventSymbol>("C.E");
Assert.Equal(isWinRtEvent, e.IsWindowsRuntimeEvent);

var i = e.ContainingType.Interfaces().Single();
var ie = i.GetMember<EventSymbol>("E");
Assert.Equal(isWinRtEvent, ie.IsWindowsRuntimeEvent);

Assert.Same(e, e.ContainingType.FindImplementationForInterfaceMember(ie));

if (module is SourceModuleSymbol)
{
Assert.True(e.IsPartialDefinition);
Assert.Equal(isWinRtEvent, ((SourceEventSymbol)e).PartialImplementationPart!.IsWindowsRuntimeEvent);
}
}
}

[Fact]
public void ExplicitInterfaceImplementation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,34 @@ partial class C
Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 24),
// (3,28): error CS0082: Type 'C' already reserves a member called 'get_P' with the same parameter types
// public partial int P { get; }
Diagnostic(ErrorCode.ERR_MemberReserved, "get").WithArguments("get_P", "C").WithLocation(3, 28)
Diagnostic(ErrorCode.ERR_MemberReserved, "get").WithArguments("get_P", "C").WithLocation(3, 28),
// (4,24): error CS0759: No defining declaration found for implementing declaration of partial method 'C.get_P()'
// public partial int get_P() => 1;
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "get_P").WithArguments("C.get_P()").WithLocation(4, 24)
);
}

[Fact]
public void DuplicateDeclaration_07b()
{
var source = """
partial class C
{
public partial int get_P() => 1;
public partial int P { get; }
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (3,24): error CS0759: No defining declaration found for implementing declaration of partial method 'C.get_P()'
// public partial int get_P() => 1;
Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "get_P").WithArguments("C.get_P()").WithLocation(3, 24),
// (4,24): error CS9248: Partial property 'C.P' must have an implementation part.
// public partial int P { get; }
Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(4, 24),
// (4,28): error CS0082: Type 'C' already reserves a member called 'get_P' with the same parameter types
// public partial int P { get; }
Diagnostic(ErrorCode.ERR_MemberReserved, "get").WithArguments("get_P", "C").WithLocation(4, 28)
);
}

Expand Down
15 changes: 15 additions & 0 deletions src/Compilers/Test/Core/CompilationVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ internal CompilationVerifier(

private EmitData GetEmitData() => _emitData ?? throw new InvalidOperationException("Must call Emit first");

internal PortableExecutableReference GetImageReference(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 inspired by GetCompilation(...).EmitToImageReference - there was no equivalent for CompileAndVerify afaict

bool embedInteropTypes = false,
ImmutableArray<string> aliases = default,
DocumentationProvider? documentation = null)
{
if (Compilation.Options.OutputKind == OutputKind.NetModule)
{
return ModuleMetadata.CreateFromImage(EmittedAssemblyData).GetReference(documentation, display: Compilation.MakeSourceModuleName());
}
else
{
return AssemblyMetadata.CreateFromImage(EmittedAssemblyData).GetReference(documentation, aliases: aliases, embedInteropTypes: embedInteropTypes, display: Compilation.MakeSourceAssemblySimpleName());
}
}

internal Metadata GetMetadata()
{
var emitData = GetEmitData();
Expand Down