-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance explicit interface implementation completion provider #75568
base: main
Are you sure you want to change the base?
Changes from all commits
219a780
dfb7cfb
c6bdfec
8b3936c
7b1a029
c80b651
b7705b7
5afac24
1740ae0
167adf2
0f0027c
18aae6b
bb2eaa2
597408a
1d6eb9d
5c3132c
2dc4ecf
6717df7
40cd75c
3cd8104
d68cd2b
f3f028d
3ab46c5
e94f2cf
062b705
07a8599
88b726e
cc673cb
0bce3fd
1ae949f
7b0f668
de4e2c8
5bab752
381b125
58e7e86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4663,81 +4663,87 @@ public async Task TestOptionalDateTime1() | |
await new VerifyCS.Test | ||
{ | ||
TestCode = """ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
|
||
public class C : {|CS0535:IGoo|} | ||
{ | ||
} | ||
""", | ||
public class C : {|CS0535:IGoo|} | ||
{ | ||
} | ||
""", | ||
FixedCode = """ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
|
||
public class C : IGoo | ||
{ | ||
public void Goo([DateTimeConstant(100), Optional] DateTime x) | ||
public class C : IGoo | ||
{ | ||
throw new NotImplementedException(); | ||
public void Goo([DateTimeConstant(100), Optional] DateTime x) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} | ||
} | ||
""", | ||
""", | ||
Options = { AllOptionsOff }, | ||
|
||
// 🐛 one value is generated with 0L instead of 0 | ||
// 🐛 one value is generated with 100L instead of 100L | ||
CodeActionValidationMode = CodeActionValidationMode.None, | ||
}.RunAsync(); | ||
} | ||
|
||
[Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545476")] | ||
public async Task TestOptionalDateTime2() | ||
{ | ||
await TestWithAllCodeStyleOptionsOffAsync( | ||
""" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
await new VerifyCS.Test | ||
{ | ||
TestCode = """ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
|
||
public class C : {|CS0535:IGoo|} | ||
{ | ||
} | ||
""", | ||
""" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
public class C : {|CS0535:IGoo|} | ||
{ | ||
} | ||
""", | ||
FixedCode = """ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
interface IGoo | ||
{ | ||
void Goo([Optional][DateTimeConstant(100)] DateTime x); | ||
} | ||
|
||
public class C : IGoo | ||
{ | ||
void IGoo.Goo(DateTime x) | ||
public class C : IGoo | ||
{ | ||
throw new NotImplementedException(); | ||
void IGoo.Goo([DateTimeConstant(100), Optional] DateTime x) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} | ||
} | ||
""", | ||
codeAction: ("True;False;False:global::IGoo;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1)); | ||
""", | ||
Options = { AllOptionsOff }, | ||
|
||
CodeActionEquivalenceKey = "True;False;False:global::IGoo;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", | ||
// 🐛 one value is generated with 0L instead of 0 | ||
CodeActionValidationMode = CodeActionValidationMode.None, | ||
}.RunAsync(); | ||
} | ||
|
||
[Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545477")] | ||
|
@@ -4813,12 +4819,12 @@ interface IGoo | |
|
||
public class C : IGoo | ||
{ | ||
void IGoo.Goo1(object x) | ||
void IGoo.Goo1([IUnknownConstant, Optional] object x) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
||
void IGoo.Goo2(object x) | ||
void IGoo.Goo2([IDispatchConstant, Optional] object x) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
@@ -6726,7 +6732,8 @@ interface I | |
|
||
class C : I | ||
{ | ||
bool I.Goo(bool x) | ||
[return: MarshalAs(UnmanagedType.U1)] | ||
bool I.Goo([MarshalAs(UnmanagedType.U1)] bool x) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
@@ -7622,6 +7629,29 @@ public List<UU> M<TT, UU>(Dictionary<TT, List<UU>> a, TT b, UU c) where UU : TT | |
[Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/994328")] | ||
public async Task TestDisposePatternWhenAdditionalUsingsAreIntroduced2() | ||
{ | ||
const string equatableParameter = | ||
#if NET9_0_OR_GREATER | ||
"[AllowNull] int other" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems... undesirable. why have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll crosscheck with the explicit interface implementation refactoring to see whether this is the existing behavior There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. waiting on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check the test |
||
#else | ||
"int other" | ||
#endif | ||
; | ||
|
||
const string resultUsings = | ||
#if NET9_0_OR_GREATER | ||
""" | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
""" | ||
#else | ||
""" | ||
using System; | ||
using System.Collections.Generic; | ||
""" | ||
#endif | ||
; | ||
|
||
await TestWithAllCodeStyleOptionsOffAsync( | ||
""" | ||
interface I<T, U> : System.IDisposable, System.IEquatable<int> where U : T | ||
|
@@ -7639,8 +7669,7 @@ partial class C | |
} | ||
""", | ||
$$""" | ||
using System; | ||
using System.Collections.Generic; | ||
{{resultUsings}} | ||
|
||
interface I<T, U> : System.IDisposable, System.IEquatable<int> where U : T | ||
{ | ||
|
@@ -7652,7 +7681,7 @@ partial class C : I<System.Exception, System.AggregateException>, System.IDispos | |
{ | ||
private bool disposedValue; | ||
|
||
bool IEquatable<int>.Equals(int other) | ||
bool IEquatable<int>.Equals({{equatableParameter}}) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
@@ -11538,12 +11567,12 @@ class C3 : I1<C3> | |
throw new System.NotImplementedException(); | ||
} | ||
|
||
public static explicit operator checked string(C3 x) | ||
static explicit I1<C3>.operator checked string(C3 x) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
||
public static explicit operator string(C3 x) | ||
static explicit I1<C3>.operator string(C3 x) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,11 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.Editing; | ||
|
@@ -11,6 +15,7 @@ | |
using Microsoft.CodeAnalysis.Internal.Log; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
using Microsoft.CodeAnalysis.Shared.Extensions; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.ImplementInterface; | ||
|
||
|
@@ -43,7 +48,6 @@ public async Task<Document> ImplementInterfaceAsync( | |
if (state == null) | ||
return document; | ||
|
||
// TODO: https://github.com/dotnet/roslyn/issues/60990 | ||
// While implementing just one default action, like in the case of pressing enter after interface name in VB, | ||
// choose to implement with the dispose pattern as that's the Dev12 behavior. | ||
var implementDisposePattern = ShouldImplementDisposePattern(model.Compilation, state, explicitly: false); | ||
|
@@ -94,4 +98,59 @@ public async Task<Document> ImplementInterfaceAsync( | |
this, document, info, options, configuration); | ||
return await generator.ImplementInterfaceAsync(cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
public async Task<ISymbol> ExplicitlyImplementSingleInterfaceMemberAsync( | ||
Document document, | ||
IImplementInterfaceInfo info, | ||
ISymbol member, | ||
ImplementTypeOptions options, | ||
CancellationToken cancellationToken) | ||
{ | ||
var configuration = new ImplementInterfaceConfiguration() | ||
{ | ||
Abstractly = false, | ||
Explicitly = true, | ||
OnlyRemaining = false, | ||
ImplementDisposePattern = false, | ||
ThroughMember = null, | ||
}; | ||
var generator = new ImplementInterfaceGenerator( | ||
this, document, info, options, configuration); | ||
var implementedMembers = await generator.GenerateExplicitlyImplementedMembersAsync(member, options.PropertyGenerationBehavior, cancellationToken).ConfigureAwait(false); | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
var singleImplemented = implementedMembers[0]; | ||
Contract.ThrowIfNull(singleImplemented); | ||
|
||
// Since non-indexer properties are the only symbols that get their implementing accessor symbols returned, | ||
// we have to process the created symbols and reduce to the single property wherein the accessors are contained | ||
if (member is IPropertySymbol { IsIndexer: false }) | ||
{ | ||
IPropertySymbol? commonContainer = null; | ||
foreach (var implementedMember in implementedMembers) | ||
{ | ||
if (implementedMember is IPropertySymbol implementedProperty) | ||
{ | ||
commonContainer ??= implementedProperty; | ||
Contract.ThrowIfFalse(commonContainer == implementedProperty, "We should have a common property implemented"); | ||
} | ||
else | ||
{ | ||
Contract.ThrowIfNull(implementedMember); | ||
var containingProperty = implementedMember!.ContainingSymbol as IPropertySymbol; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the !, you just threw if it was null. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing the ! |
||
Contract.ThrowIfNull(containingProperty); | ||
commonContainer ??= containingProperty; | ||
Contract.ThrowIfFalse(commonContainer == containingProperty, "We should have a common property implemented"); | ||
} | ||
} | ||
Contract.ThrowIfNull(commonContainer); | ||
singleImplemented = commonContainer; | ||
} | ||
else | ||
{ | ||
Contract.ThrowIfFalse(implementedMembers.Length == 1, "We missed another case that may return multiple symbols"); | ||
} | ||
|
||
return singleImplemented; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what this means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also
TestIntegralAndFloatLiterals
; when specifyingDateTimeConstant(100)
, the semantic structure equivalence check does not forgive the mismatching literal types, despite the available conversion. Removing the CodeActionValidationMode causes the test to fail saying that"100" does not equal "100"
, where the one is int and the other is long. Apparently the generated argument is perceived as beingint
when the attribute's argument expects a long. I have not dug up the real cause for this problem, so I just copied the note from the other test to indicate the problem.