Skip to content

Commit 04fb391

Browse files
authored
Require AddExplicitInterfaceImplementation for adding a type that implements member explicitly (#73206)
1 parent f62997b commit 04fb391

File tree

12 files changed

+207
-81
lines changed

12 files changed

+207
-81
lines changed

src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10453,7 +10453,7 @@ static IEnumerable<int> F()
1045310453
// should not contain RUDE_EDIT_INSERT_AROUND
1045410454
edits.VerifySemanticDiagnostics(
1045510455
active,
10456-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
10456+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1045710457
}
1045810458

1045910459
[Fact]
@@ -10484,7 +10484,7 @@ static IEnumerable<int> F()
1048410484

1048510485
edits.VerifySemanticDiagnostics(
1048610486
active,
10487-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
10487+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1048810488
}
1048910489

1049010490
[Fact]
@@ -10667,7 +10667,7 @@ static async Task<int> F()
1066710667
// should not contain RUDE_EDIT_INSERT_AROUND
1066810668
edits.VerifySemanticDiagnostics(
1066910669
active,
10670-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
10670+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1067110671
}
1067210672

1067310673
[Fact]
@@ -10758,7 +10758,7 @@ static async Task<int> F()
1075810758

1075910759
edits.VerifySemanticDiagnostics(
1076010760
active,
10761-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
10761+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1076210762
}
1076310763

1076410764
[Fact]
@@ -10786,7 +10786,7 @@ static async void F()
1078610786
var active = GetActiveStatements(src1, src2);
1078710787

1078810788
edits.VerifySemanticDiagnostics(active,
10789-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
10789+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1079010790
}
1079110791

1079210792
[Fact]

src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6569,7 +6569,7 @@ public void F()
65696569
capabilities: EditAndContinueCapabilities.Baseline);
65706570

65716571
edits.VerifySemanticDiagnostics(
6572-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
6572+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
65736573
}
65746574

65756575
#endregion
@@ -9615,7 +9615,7 @@ async Task<int> WaitAsync()
96159615
capabilities: EditAndContinueCapabilities.Baseline);
96169616

96179617
edits.VerifySemanticDiagnostics(
9618-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
9618+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
96199619
}
96209620

96219621
[Fact]
@@ -9649,7 +9649,7 @@ IEnumerable<int> Iter()
96499649
capabilities: EditAndContinueCapabilities.Baseline);
96509650

96519651
edits.VerifySemanticDiagnostics(
9652-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
9652+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
96539653
}
96549654

96559655
[Fact]
@@ -11484,7 +11484,7 @@ static IEnumerable<int> F()
1148411484

1148511485
edits.VerifySemanticDiagnostics(
1148611486
targetFrameworks: [TargetFramework.Mscorlib40AndSystemCore],
11487-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
11487+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1148811488
}
1148911489

1149011490
#endregion
@@ -12079,7 +12079,7 @@ class C
1207912079
var edits = GetTopEdits(src1, src2);
1208012080

1208112081
edits.VerifySemanticDiagnostics(
12082-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
12082+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1208312083
}
1208412084

1208512085
[Fact]
@@ -12431,7 +12431,7 @@ static async Task<int> F()
1243112431

1243212432
edits.VerifySemanticDiagnostics(
1243312433
targetFrameworks: [TargetFramework.MinimalAsync],
12434-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
12434+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
1243512435
}
1243612436

1243712437
[Fact]

src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs

Lines changed: 132 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,70 +1968,132 @@ public void Struct_Modifiers_Partial_InsertDelete(string modifier)
19681968
}
19691969

19701970
[Fact]
1971-
public void Class_ImplementingInterface_Add()
1971+
public void Class_ImplementingInterface_Add_Implicit_NonVirtual()
19721972
{
1973-
var src1 = @"
1974-
using System;
1973+
var src1 = """
1974+
interface I
1975+
{
1976+
void F();
1977+
}
1978+
""";
19751979

1976-
public interface ISample
1977-
{
1978-
string Get();
1979-
}
1980+
var src2 = """
1981+
interface I
1982+
{
1983+
void F();
1984+
}
19801985

1981-
public interface IConflict
1982-
{
1983-
string Get();
1984-
}
1986+
class C : I
1987+
{
1988+
public void F() {}
1989+
}
1990+
""";
19851991

1986-
public class BaseClass : ISample
1987-
{
1988-
public virtual string Get() => string.Empty;
1989-
}
1990-
";
1991-
var src2 = @"
1992-
using System;
1992+
var edits = GetTopEdits(src1, src2);
1993+
edits.VerifySemantics(
1994+
[SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))],
1995+
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
1996+
}
19931997

1994-
public interface ISample
1995-
{
1996-
string Get();
1997-
}
1998+
[Fact]
1999+
public void Class_ImplementingInterface_Add_Implicit_Virtual()
2000+
{
2001+
var src1 = """
2002+
interface I
2003+
{
2004+
void F();
2005+
}
2006+
""";
19982007

1999-
public interface IConflict
2000-
{
2001-
string Get();
2002-
}
2008+
var src2 = """
2009+
interface I
2010+
{
2011+
void F();
2012+
}
20032013

2004-
public class BaseClass : ISample
2005-
{
2006-
public virtual string Get() => string.Empty;
2007-
}
2014+
class C : I
2015+
{
2016+
public virtual void F() {}
2017+
}
2018+
""";
20082019

2009-
public class SubClass : BaseClass, IConflict
2010-
{
2011-
public override string Get() => string.Empty;
2020+
var edits = GetTopEdits(src1, src2);
2021+
edits.VerifySemantics(
2022+
[SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))],
2023+
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
2024+
}
20122025

2013-
string IConflict.Get() => String.Empty;
2014-
}
2015-
";
2026+
[Fact]
2027+
public void Class_ImplementingInterface_Add_Implicit_Override()
2028+
{
2029+
var src1 = """
2030+
interface I
2031+
{
2032+
void F();
2033+
}
2034+
2035+
class C : I
2036+
{
2037+
public virtual void F() {}
2038+
}
2039+
""";
2040+
2041+
var src2 = """
2042+
interface I
2043+
{
2044+
void F();
2045+
}
2046+
2047+
class C : I
2048+
{
2049+
public virtual void F() {}
2050+
}
2051+
2052+
class D : C
2053+
{
2054+
public override void F() {}
2055+
}
2056+
""";
20162057

20172058
var edits = GetTopEdits(src1, src2);
2059+
edits.VerifySemantics(
2060+
[SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("D"))],
2061+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
2062+
}
20182063

2019-
edits.VerifyEdits(
2020-
@"Insert [public class SubClass : BaseClass, IConflict
2021-
{
2022-
public override string Get() => string.Empty;
2064+
[Theory]
2065+
[InlineData("void F();", "void I.F() {}")]
2066+
[InlineData("int F { get; }", "int I.F { get; }")]
2067+
[InlineData("event System.Action F;", "event System.Action I.F { add {} remove {} }")]
2068+
public void Class_ImplementingInterface_Add_Explicit_NonVirtual(string memberDef, string explicitImpl)
2069+
{
2070+
var src1 = $$"""
2071+
interface I
2072+
{
2073+
{{memberDef}}
2074+
}
2075+
""";
20232076

2024-
string IConflict.Get() => String.Empty;
2025-
}]@219",
2026-
"Insert [: BaseClass, IConflict]@241",
2027-
"Insert [public override string Get() => string.Empty;]@272",
2028-
"Insert [string IConflict.Get() => String.Empty;]@325",
2029-
"Insert [()]@298",
2030-
"Insert [()]@345");
2077+
var src2 = $$"""
2078+
interface I
2079+
{
2080+
{{memberDef}}
2081+
}
2082+
2083+
class C<T> : I
2084+
{
2085+
{{explicitImpl}}
2086+
}
2087+
""";
2088+
2089+
var edits = GetTopEdits(src1, src2);
2090+
2091+
edits.VerifySemantics(
2092+
[SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))],
2093+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
20312094

2032-
// Here we add a class implementing an interface and a method inside it with explicit interface specifier.
2033-
// We want to be sure that adding the method will not tirgger a rude edit as it happens if adding a single method with explicit interface specifier.
20342095
edits.VerifySemanticDiagnostics(
2096+
[Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "class C<T>", GetResource("class"))],
20352097
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
20362098
}
20372099

@@ -2312,15 +2374,28 @@ void F() {}
23122374
public void Type_Generic_InsertMembers_Reloadable()
23132375
{
23142376
var src1 = ReloadableAttributeSrc + @"
2377+
interface IExplicit
2378+
{
2379+
void F() {}
2380+
}
2381+
23152382
[CreateNewOnMetadataUpdate]
2316-
class C<T>
2383+
class C<T> : IExplicit
23172384
{
2385+
void IExplicit.F() {}
23182386
}
23192387
";
23202388
var src2 = ReloadableAttributeSrc + @"
2389+
interface IExplicit
2390+
{
2391+
void F() {}
2392+
}
2393+
23212394
[CreateNewOnMetadataUpdate]
2322-
class C<T>
2395+
class C<T> : IExplicit
23232396
{
2397+
void IExplicit.F() {}
2398+
23242399
void M() {}
23252400
int P1 { get; set; }
23262401
int P2 { get => 1; set {} }
@@ -2337,6 +2412,10 @@ class D {}
23372412
var edits = GetTopEdits(src1, src2);
23382413
edits.VerifySemantics(
23392414
[SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"))],
2415+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
2416+
2417+
edits.VerifySemanticDiagnostics(
2418+
[Diagnostic(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, "void M()", "CreateNewOnMetadataUpdateAttribute")],
23402419
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
23412420
}
23422421

@@ -7534,7 +7613,7 @@ public async Task<int> WaitAsync()
75347613
}";
75357614
var edits = GetTopEdits(src1, src2);
75367615
edits.VerifySemanticDiagnostics(
7537-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
7616+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
75387617

75397618
VerifyPreserveLocalVariables(edits, preserveLocalVariables: false);
75407619
}
@@ -9667,7 +9746,7 @@ public void MethodUpdate_AddYieldReturn()
96679746
var edits = GetTopEdits(src1, src2);
96689747

96699748
edits.VerifySemanticDiagnostics(
9670-
capabilities: EditAndContinueCapabilities.NewTypeDefinition);
9749+
capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation);
96719750

96729751
VerifyPreserveLocalVariables(edits, preserveLocalVariables: false);
96739752
}

src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,7 +2573,7 @@ private async Task<ImmutableArray<SemanticEditInfo>> AnalyzeSemanticsAsync(
25732573
{
25742574
if (processedSymbols.Add(newContainingType))
25752575
{
2576-
if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
2576+
if (capabilities.GrantNewTypeDefinition(containingType))
25772577
{
25782578
semanticEdits.Add(SemanticEditInfo.CreateReplace(containingTypeSymbolKey,
25792579
IsPartialTypeEdit(oldContainingType, newContainingType, oldTree, newTree) ? containingTypeSymbolKey : null));
@@ -2607,7 +2607,7 @@ private async Task<ImmutableArray<SemanticEditInfo>> AnalyzeSemanticsAsync(
26072607
// https://github.com/dotnet/roslyn/issues/54881
26082608
diagnosticContext.Report(RudeEditKind.ChangingTypeParameters, cancellationToken);
26092609
}
2610-
else if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
2610+
else if (!capabilities.GrantNewTypeDefinition(oldType))
26112611
{
26122612
diagnosticContext.Report(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, cancellationToken);
26132613
}
@@ -2889,7 +2889,7 @@ newSymbol is IPropertySymbol newProperty &&
28892889
// therefore inserting the <Program>$ type
28902890
Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol || IsGlobalMain(newSymbol));
28912891

2892-
if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
2892+
if (!capabilities.GrantNewTypeDefinition((newSymbol as INamedTypeSymbol) ?? newSymbol.ContainingType))
28932893
{
28942894
diagnostics.Add(new RudeEditDiagnostic(
28952895
RudeEditKind.InsertNotSupportedByRuntime,
@@ -3211,7 +3211,7 @@ IFieldSymbol or
32113211
{
32123212
if (processedSymbols.Add(newContainingType))
32133213
{
3214-
if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
3214+
if (capabilities.GrantNewTypeDefinition(newContainingType))
32153215
{
32163216
var oldContainingTypeKey = SymbolKey.Create(oldContainingType, cancellationToken);
32173217
semanticEdits.Add(SemanticEditInfo.CreateReplace(oldContainingTypeKey,
@@ -3895,7 +3895,7 @@ private void ReportMemberOrLambdaBodyUpdateRudeEdits(
38953895

38963896
if (!oldStateMachineInfo.IsStateMachine &&
38973897
newStateMachineInfo.IsStateMachine &&
3898-
!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
3898+
!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation))
38993899
{
39003900
// Adding a state machine, either for async or iterator, will require creating a new helper class
39013901
// so is a rude edit if the runtime doesn't support it
@@ -5690,9 +5690,11 @@ bool CanAddNewLambda(SyntaxNode newLambda, LambdaBody newLambdaBody1, LambdaBody
56905690
}
56915691
}
56925692

5693-
// If the old verison of the method had any lambdas the nwe know a closure type exists and a new one isn't needed.
5693+
// If the old version of the method had any lambdas then we know a closure type exists and a new one isn't needed.
56945694
// We also know that adding a local function won't create a new closure type.
56955695
// Otherwise, we assume a new type is needed.
5696+
// We also assume that the closure type does not implement an interface explicitly,
5697+
// so we do not need AddExplicitInterfaceImplementation capability.
56965698

56975699
if (!oldHasLambdas && !isLocalFunction)
56985700
{

0 commit comments

Comments
 (0)