Skip to content

Commit

Permalink
Fix explicit generation of static conversion methods in interfaces (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Nov 23, 2024
2 parents fe4c98a + 56b2cde commit 36754e8
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ImplementInterface;
[ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementAbstractClass)]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class CSharpImplementInterfaceCodeFixProvider() : AbstractImplementInterfaceCodeFixProvider<TypeSyntax>
internal sealed class CSharpImplementInterfaceCodeFixProvider()
: AbstractImplementInterfaceCodeFixProvider<TypeSyntax>
{
private const string CS0535 = nameof(CS0535); // 'Program' does not implement interface member 'System.Collections.IEnumerable.GetEnumerator()'
private const string CS0737 = nameof(CS0737); // 'Class' does not implement interface member 'IInterface.M()'. 'Class.M()' cannot implement an interface member because it is not public.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.ImplementInterface;
Expand Down Expand Up @@ -377,7 +376,7 @@ class Class : IInterface
}
}
""",
codeAction: ("True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1));
codeAction: ("True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", 1));
}

[Fact, CompilerTrait(CompilerFeature.Tuples)]
Expand Down Expand Up @@ -11538,12 +11537,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();
}
Expand Down Expand Up @@ -11933,4 +11932,48 @@ event System.EventHandler I.Click
}
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/61263")]
public async Task ImplementStaticConversionsExplicitly()
{
await new VerifyCS.Test
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
TestCode = """
interface I11<T11> where T11 : I11<T11>
{
static abstract implicit operator long(T11 x);
static abstract explicit operator int(T11 x);
}

class C11 : {|CS0535:{|CS0535:I11<C11>|}|}
{
}
""",
FixedCode = """
interface I11<T11> where T11 : I11<T11>
{
static abstract implicit operator long(T11 x);
static abstract explicit operator int(T11 x);
}

class C11 : I11<C11>
{
static implicit I11<C11>.operator long(C11 x)
{
throw new System.NotImplementedException();
}

static explicit I11<C11>.operator int(C11 x)
{
throw new System.NotImplementedException();
}
}
""",
CodeActionIndex = 1,
CodeActionEquivalenceKey = "True;False;False:global::I11<global::C11>;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;",
CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title),
}.RunAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
Expand Down Expand Up @@ -48,34 +49,31 @@ private static ConversionOperatorDeclarationSyntax GenerateConversionDeclaration
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
var hasNoBody = !info.Context.GenerateMethodBodies || method.IsExtern;

var reusableSyntax = GetReuseableSyntaxNodeForSymbol<ConversionOperatorDeclarationSyntax>(method, info);
if (reusableSyntax != null)
{
return reusableSyntax;
}

var keyword = method.MetadataName == WellKnownMemberNames.ImplicitConversionName
? ImplicitKeyword
: ExplicitKeyword;

var checkedToken = SyntaxFacts.IsCheckedOperator(method.MetadataName)
var checkedKeyword = SyntaxFacts.IsCheckedOperator(method.MetadataName)
? CheckedKeyword
: default;

var hasNoBody = !info.Context.GenerateMethodBodies || method.IsExtern;
var declaration = ConversionOperatorDeclaration(
attributeLists: AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), info),
modifiers: GenerateModifiers(destination),
modifiers: GenerateModifiers(destination, method),
implicitOrExplicitKeyword: keyword,
explicitInterfaceSpecifier: null,
explicitInterfaceSpecifier: GenerateExplicitInterfaceSpecifier(method.ExplicitInterfaceImplementations),
operatorKeyword: OperatorKeyword,
checkedKeyword: checkedToken,
checkedKeyword: checkedKeyword,
type: method.ReturnType.GenerateTypeSyntax(),
parameterList: ParameterGenerator.GenerateParameterList(method.Parameters, isExplicit: false, info: info),
body: hasNoBody ? null : StatementGenerator.GenerateBlock(method),
expressionBody: null,
semicolonToken: hasNoBody ? SemicolonToken : new SyntaxToken());
semicolonToken: hasNoBody ? SemicolonToken : default);

declaration = UseExpressionBodyIfDesired(info, declaration, cancellationToken);

Expand All @@ -100,11 +98,19 @@ private static ConversionOperatorDeclarationSyntax UseExpressionBodyIfDesired(
return declaration;
}

private static SyntaxTokenList GenerateModifiers(CodeGenerationDestination destination)
private static SyntaxTokenList GenerateModifiers(CodeGenerationDestination destination, IMethodSymbol method)
{
// If these appear in interfaces they must be static abstract
return destination is CodeGenerationDestination.InterfaceType
? ([StaticKeyword, AbstractKeyword])
: ([PublicKeyword, StaticKeyword]);
// Only "static" allowed if we're an explicit impl.
if (method.ExplicitInterfaceImplementations.Any())
{
return method.IsStatic ? [StaticKeyword] : [];
}
else
{
// If these appear in interfaces they must be static abstract
return destination is CodeGenerationDestination.InterfaceType
? ([StaticKeyword, AbstractKeyword])
: ([PublicKeyword, StaticKeyword]);
}
}
}

0 comments on commit 36754e8

Please sign in to comment.