From e1c570cfe52f08d392791681b379c07285f362d6 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Tue, 12 Apr 2022 18:28:32 -0700 Subject: [PATCH] Add implement interface support for checked operators and cast operators --- .../ImplementInterfaceTests.cs | 273 ++++++++++++++++-- .../CodeGeneration/ConversionGenerator.cs | 7 + .../CodeGeneration/OperatorGenerator.cs | 4 + .../Extensions/INamedTypeSymbolExtensions.cs | 2 +- 4 files changed, 261 insertions(+), 25 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index f3380570364e8..5b0b6c355c7b2 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -9934,12 +9934,12 @@ public int Foo } [WorkItem(53925, "https://github.com/dotnet/roslyn/issues/53925")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceMember() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest @@ -9972,12 +9972,12 @@ public static void M1() } [WorkItem(53925, "https://github.com/dotnet/roslyn/issues/53925")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceMemberExplicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest @@ -10010,12 +10010,12 @@ static void ITest.M1() } [WorkItem(53925, "https://github.com/dotnet/roslyn/issues/53925")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceMember_ImplementAbstractly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest @@ -10048,12 +10048,12 @@ public static void M1() } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceOperator_OnlyExplicitlyImplementable() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest @@ -10084,12 +10084,12 @@ class C : ITest } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceOperator_ImplementImplicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest where T : ITest @@ -10127,12 +10127,12 @@ class C : ITest } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceOperator_ImplementExplicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest where T : ITest @@ -10163,12 +10163,12 @@ class C : ITest } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterfaceOperator_ImplementAbstractly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest where T : ITest @@ -10200,12 +10200,12 @@ abstract class C : ITest } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterface_Explicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest @@ -10237,12 +10237,12 @@ static int ITest.M(ITest x) } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterface_Implicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest @@ -10274,12 +10274,12 @@ public static int M(ITest x) } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterface_ImplementImplicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest where T : ITest @@ -10311,12 +10311,12 @@ public static int M(C x) } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterface_ImplementExplicitly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest where T : ITest @@ -10348,12 +10348,12 @@ static int ITest.M(C x) } [WorkItem(53927, "https://github.com/dotnet/roslyn/issues/53927")] - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/56171"), Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] public async Task TestStaticAbstractInterface_ImplementAbstractly() { await new VerifyCS.Test { - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, LanguageVersion = LanguageVersion.Preview, TestCode = @" interface ITest where T : ITest @@ -10383,5 +10383,230 @@ public static int M(C x) }.RunAsync(); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [WorkItem(60214, "https://github.com/dotnet/roslyn/issues/60214")] + public async Task TestImplementCheckedOperators_Explicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface I1 where T : I1 +{ + abstract static explicit operator checked string(T x); + abstract static explicit operator string(T x); + + abstract static T operator checked -(T x); + abstract static T operator -(T x); + + abstract static T operator checked +(T x, T y); + abstract static T operator +(T x, T y); +} + +class C3 : {|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:I1|}|}|}|}|}|} +{ +}", + FixedCode = @" +interface I1 where T : I1 +{ + abstract static explicit operator checked string(T x); + abstract static explicit operator string(T x); + + abstract static T operator checked -(T x); + abstract static T operator -(T x); + + abstract static T operator checked +(T x, T y); + abstract static T operator +(T x, T y); +} + +class C3 : I1 +{ + static C3 I1.operator checked +(C3 x, C3 y) + { + throw new System.NotImplementedException(); + } + + static C3 I1.operator +(C3 x, C3 y) + { + throw new System.NotImplementedException(); + } + + static C3 I1.operator checked -(C3 x) + { + throw new System.NotImplementedException(); + } + + static C3 I1.operator -(C3 x) + { + throw new System.NotImplementedException(); + } + + public static explicit operator checked string(C3 x) + { + throw new System.NotImplementedException(); + } + + public static explicit operator string(C3 x) + { + throw new System.NotImplementedException(); + } +}", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_all_members_explicitly, codeAction.Title), + CodeActionEquivalenceKey = "True;False;False:global::I1;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [WorkItem(60214, "https://github.com/dotnet/roslyn/issues/60214")] + public async Task TestImplementCheckedOperators_Implicitly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface I1 where T : I1 +{ + abstract static explicit operator checked string(T x); + abstract static explicit operator string(T x); + + abstract static T operator checked -(T x); + abstract static T operator -(T x); + + abstract static T operator checked +(T x, T y); + abstract static T operator +(T x, T y); +} + +class C3 : {|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:I1|}|}|}|}|}|} +{ +}", + FixedCode = @" +interface I1 where T : I1 +{ + abstract static explicit operator checked string(T x); + abstract static explicit operator string(T x); + + abstract static T operator checked -(T x); + abstract static T operator -(T x); + + abstract static T operator checked +(T x, T y); + abstract static T operator +(T x, T y); +} + +class C3 : I1 +{ + public static C3 operator checked +(C3 x, C3 y) + { + throw new System.NotImplementedException(); + } + + public static C3 operator +(C3 x, C3 y) + { + throw new System.NotImplementedException(); + } + + public static C3 operator checked -(C3 x) + { + throw new System.NotImplementedException(); + } + + public static C3 operator -(C3 x) + { + throw new System.NotImplementedException(); + } + + public static explicit operator checked string(C3 x) + { + throw new System.NotImplementedException(); + } + + public static explicit operator string(C3 x) + { + throw new System.NotImplementedException(); + } +}", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface, codeAction.Title), + CodeActionEquivalenceKey = "False;False;True:global::I1;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 0, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [WorkItem(60214, "https://github.com/dotnet/roslyn/issues/60214")] + public async Task TestImplementCheckedOperators_Abstractly() + { + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = LanguageVersion.Preview, + TestCode = @" +interface I1 where T : I1 +{ + abstract static explicit operator checked string(T x); + abstract static explicit operator string(T x); + + abstract static T operator checked -(T x); + abstract static T operator -(T x); + + abstract static T operator checked +(T x, T y); + abstract static T operator +(T x, T y); +} + +abstract class C3 : {|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:{|CS0535:I1|}|}|}|}|}|} +{ +}", + FixedCode = @" +interface I1 where T : I1 +{ + abstract static explicit operator checked string(T x); + abstract static explicit operator string(T x); + + abstract static T operator checked -(T x); + abstract static T operator -(T x); + + abstract static T operator checked +(T x, T y); + abstract static T operator +(T x, T y); +} + +abstract class C3 : I1 +{ + public static C3 operator checked +(C3 x, C3 y) + { + throw new System.NotImplementedException(); + } + + public static C3 operator +(C3 x, C3 y) + { + throw new System.NotImplementedException(); + } + + public static C3 operator checked -(C3 x) + { + throw new System.NotImplementedException(); + } + + public static C3 operator -(C3 x) + { + throw new System.NotImplementedException(); + } + + public static explicit operator checked string(C3 x) + { + throw new System.NotImplementedException(); + } + + public static explicit operator string(C3 x) + { + throw new System.NotImplementedException(); + } +}", + CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Implement_interface_abstractly, codeAction.Title), + CodeActionEquivalenceKey = "False;True;True:global::I1;TestProject;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", + CodeActionIndex = 1, + }.RunAsync(); + } } } diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/ConversionGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/ConversionGenerator.cs index d31e66edb4257..450bfe852460d 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/ConversionGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/ConversionGenerator.cs @@ -55,14 +55,21 @@ private static ConversionOperatorDeclarationSyntax GenerateConversionDeclaration ? SyntaxFactory.Token(SyntaxKind.ImplicitKeyword) : SyntaxFactory.Token(SyntaxKind.ExplicitKeyword); + var checkedToken = SyntaxFacts.IsCheckedOperator(method.MetadataName) + ? SyntaxFactory.Token(SyntaxKind.CheckedKeyword) + : default; + var declaration = SyntaxFactory.ConversionOperatorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), options), modifiers: GenerateModifiers(), implicitOrExplicitKeyword: keyword, + explicitInterfaceSpecifier: null, operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), + checkedKeyword: checkedToken, type: method.ReturnType.GenerateTypeSyntax(), parameterList: ParameterGenerator.GenerateParameterList(method.Parameters, isExplicit: false, options: options), body: hasNoBody ? null : StatementGenerator.GenerateBlock(method), + expressionBody: null, semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : new SyntaxToken()); declaration = UseExpressionBodyIfDesired(options, declaration); diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs index 0da359866f515..20871168574ec 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/OperatorGenerator.cs @@ -80,6 +80,9 @@ private static OperatorDeclarationSyntax GenerateOperatorDeclarationWorker( } var operatorToken = SyntaxFactory.Token(operatorSyntaxKind); + var checkedToken = SyntaxFacts.IsCheckedOperator(method.MetadataName) + ? SyntaxFactory.Token(SyntaxKind.CheckedKeyword) + : default; var operatorDecl = SyntaxFactory.OperatorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), options), @@ -87,6 +90,7 @@ private static OperatorDeclarationSyntax GenerateOperatorDeclarationWorker( returnType: method.ReturnType.GenerateTypeSyntax(), explicitInterfaceSpecifier: GenerateExplicitInterfaceSpecifier(method.ExplicitInterfaceImplementations), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), + checkedKeyword: checkedToken, operatorToken: operatorToken, parameterList: ParameterGenerator.GenerateParameterList(method.Parameters, isExplicit: false, options: options), body: hasNoBody ? null : StatementGenerator.GenerateBlock(method), diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs index dd3b4c38cd049..bb2582965ce29 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs @@ -413,7 +413,7 @@ private static ImmutableArray GetUnimplementedMembers( { var q = from m in interfaceMemberGetter(interfaceType, classOrStructType) where m.Kind != SymbolKind.NamedType - where m.Kind != SymbolKind.Method || ((IMethodSymbol)m).MethodKind is MethodKind.Ordinary or MethodKind.UserDefinedOperator + where m.Kind != SymbolKind.Method || ((IMethodSymbol)m).MethodKind is MethodKind.Ordinary or MethodKind.UserDefinedOperator or MethodKind.Conversion where m.Kind != SymbolKind.Property || ((IPropertySymbol)m).IsIndexer || ((IPropertySymbol)m).CanBeReferencedByName where m.Kind != SymbolKind.Event || ((IEventSymbol)m).CanBeReferencedByName where !isImplemented(classOrStructType, m, isValidImplementation, cancellationToken)