From 4ed55b30c3d32cc3adb654c1dc607f93d96f8c39 Mon Sep 17 00:00:00 2001 From: Manuel Menegazzo <65919883+m3nax@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:36:45 +0100 Subject: [PATCH] FIX: Actor source generator generates invalid code for generic interfaces (#1419) * Handled generic actor interface Signed-off-by: Manuel Menegazzo * Added more actor examples Signed-off-by: Manuel Menegazzo * Updated actor namespace in example project Signed-off-by: Manuel Menegazzo --------- Signed-off-by: Manuel Menegazzo Co-authored-by: Whit Waldo Signed-off-by: Siri Varma Vegiraju --- .../ActorClient/IGenericClientActor.cs | 27 +++ .../ActorClientGenerator.cs | 23 ++- .../ActorClientGeneratorTests.cs | 172 ++++++++++++++++++ 3 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 examples/GeneratedActor/ActorClient/IGenericClientActor.cs diff --git a/examples/GeneratedActor/ActorClient/IGenericClientActor.cs b/examples/GeneratedActor/ActorClient/IGenericClientActor.cs new file mode 100644 index 000000000..166f4a9ef --- /dev/null +++ b/examples/GeneratedActor/ActorClient/IGenericClientActor.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Actors.Generators; + +namespace GeneratedActor +{ + [GenerateActorClient] + internal interface IGenericClientActor + { + [ActorMethod(Name = "GetState")] + Task GetStateAsync(CancellationToken cancellationToken = default); + + [ActorMethod(Name = "SetState")] + Task SetStateAsync(TGenericType2 state, CancellationToken cancellationToken = default); + } +} diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 001604d53..0f064e801 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -161,12 +161,23 @@ private static void GenerateActorClientCode(SourceProductionContext context, Act .Append(SyntaxKind.SealedKeyword) .Select(sk => SyntaxFactory.Token(sk)); - var actorClientClassDeclaration = SyntaxFactory.ClassDeclaration(descriptor.ClientTypeName) - .WithModifiers(SyntaxFactory.TokenList(actorClientClassModifiers)) - .WithMembers(SyntaxFactory.List(actorMembers)) - .WithBaseList(SyntaxFactory.BaseList( - SyntaxFactory.Token(SyntaxKind.ColonToken), - SyntaxFactory.SeparatedList(new[] { actorClientBaseInterface }))); + var actorClientClassTypeParameters = descriptor.InterfaceType.TypeParameters + .Select(x => SyntaxFactory.TypeParameter(x.ToString())); + + var actorClientClassDeclaration = (actorClientClassTypeParameters.Count() == 0) + ? SyntaxFactory.ClassDeclaration(descriptor.ClientTypeName) + .WithModifiers(SyntaxFactory.TokenList(actorClientClassModifiers)) + .WithMembers(SyntaxFactory.List(actorMembers)) + .WithBaseList(SyntaxFactory.BaseList( + SyntaxFactory.Token(SyntaxKind.ColonToken), + SyntaxFactory.SeparatedList(new[] { actorClientBaseInterface }))) + : SyntaxFactory.ClassDeclaration(descriptor.ClientTypeName) + .WithModifiers(SyntaxFactory.TokenList(actorClientClassModifiers)) + .WithTypeParameterList(SyntaxFactory.TypeParameterList(SyntaxFactory.SeparatedList(actorClientClassTypeParameters))) + .WithMembers(SyntaxFactory.List(actorMembers)) + .WithBaseList(SyntaxFactory.BaseList( + SyntaxFactory.Token(SyntaxKind.ColonToken), + SyntaxFactory.SeparatedList(new[] { actorClientBaseInterface }))); var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(descriptor.NamespaceName)) .WithMembers(SyntaxFactory.List(new[] { actorClientClassDeclaration })) diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 4c0ef194e..3515bc8b0 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -168,6 +168,92 @@ public System.Threading.Tasks.Task TestMethod() await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); } + [Fact] + public async Task TestSingleGenericInternalInterface() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + internal interface ITestActor + { + Task TestMethod(); + } +}"; + + var generatedSource = @"// +#nullable enable +namespace Test +{ + internal sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + if (actorProxy is null) + { + throw new System.ArgumentNullException(nameof(actorProxy)); + } + + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +}"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestMultipleGenericsInternalInterface() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + internal interface ITestActor + { + Task TestMethod(); + } +}"; + + var generatedSource = @"// +#nullable enable +namespace Test +{ + internal sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + if (actorProxy is null) + { + throw new System.ArgumentNullException(nameof(actorProxy)); + } + + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +}"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + [Fact] public async Task TestRenamedClient() { @@ -211,6 +297,92 @@ public System.Threading.Tasks.Task TestMethod() await CreateTest(originalSource, "Test.MyTestActorClient.g.cs", generatedSource).RunAsync(); } + [Fact] + public async Task TestSingleGenericRenamedClient() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient(Name = ""MyTestActorClient"")] + internal interface ITestActor + { + Task TestMethod(); + } +}"; + + var generatedSource = @"// +#nullable enable +namespace Test +{ + internal sealed class MyTestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + public MyTestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + if (actorProxy is null) + { + throw new System.ArgumentNullException(nameof(actorProxy)); + } + + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +}"; + + await CreateTest(originalSource, "Test.MyTestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestMultipleGenericsRenamedClient() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient(Name = ""MyTestActorClient"")] + internal interface ITestActor + { + Task TestMethod(); + } +}"; + + var generatedSource = @"// +#nullable enable +namespace Test +{ + internal sealed class MyTestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + public MyTestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + if (actorProxy is null) + { + throw new System.ArgumentNullException(nameof(actorProxy)); + } + + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +}"; + + await CreateTest(originalSource, "Test.MyTestActorClient.g.cs", generatedSource).RunAsync(); + } + [Fact] public async Task TestCustomNamespace() {