From 087f11b060b092caf33269b50a79d360859ba7a2 Mon Sep 17 00:00:00 2001 From: Oisin Grehan Date: Mon, 12 May 2025 13:41:00 -0400 Subject: [PATCH 1/3] initial commit with tests Signed-off-by: Oisin Grehan --- .../ActorClientGenerator.cs | 57 +++++++++ .../ActorClientGeneratorTests.cs | 116 ++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 0f064e801..ceb0efd1a 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -164,6 +164,62 @@ private static void GenerateActorClientCode(SourceProductionContext context, Act var actorClientClassTypeParameters = descriptor.InterfaceType.TypeParameters .Select(x => SyntaxFactory.TypeParameter(x.ToString())); + // Create constraint clauses for type parameters + var constraintClauses = new List(); + + // For each type parameter, create constraint clauses based on the interface's constraints + foreach (var typeParam in descriptor.InterfaceType.TypeParameters) + { + if (typeParam.HasReferenceTypeConstraint || + typeParam.HasValueTypeConstraint || + typeParam.HasUnmanagedTypeConstraint || + typeParam.HasNotNullConstraint || + typeParam.ConstraintTypes.Length > 0) + { + var constraints = new List(); + + // Add class/struct constraints + if (typeParam.HasReferenceTypeConstraint) + { + constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint)); + } + else if (typeParam.HasValueTypeConstraint) + { + constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint)); + } + + // Add unmanaged constraint + if (typeParam.HasUnmanagedTypeConstraint) + { + constraints.Add(SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName("unmanaged"))); + } + + // Add type constraints (e.g., where T : IInterface) + foreach (var constraintType in typeParam.ConstraintTypes) + { + constraints.Add(SyntaxFactory.TypeConstraint( + SyntaxFactory.ParseTypeName(constraintType.ToString()))); + } + + // Add notnull constraint + if (typeParam.HasNotNullConstraint) + { + constraints.Add(SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName("notnull"))); + } + + // Add new() constraint - must be last + if (typeParam.HasConstructorConstraint) + { + constraints.Add(SyntaxFactory.ConstructorConstraint()); + } + + constraintClauses.Add( + SyntaxFactory.TypeParameterConstraintClause( + SyntaxFactory.IdentifierName(typeParam.Name), + SyntaxFactory.SeparatedList(constraints))); + } + } + var actorClientClassDeclaration = (actorClientClassTypeParameters.Count() == 0) ? SyntaxFactory.ClassDeclaration(descriptor.ClientTypeName) .WithModifiers(SyntaxFactory.TokenList(actorClientClassModifiers)) @@ -174,6 +230,7 @@ private static void GenerateActorClientCode(SourceProductionContext context, Act : SyntaxFactory.ClassDeclaration(descriptor.ClientTypeName) .WithModifiers(SyntaxFactory.TokenList(actorClientClassModifiers)) .WithTypeParameterList(SyntaxFactory.TypeParameterList(SyntaxFactory.SeparatedList(actorClientClassTypeParameters))) + .WithConstraintClauses(SyntaxFactory.List(constraintClauses)) // Add constraint clauses to the class .WithMembers(SyntaxFactory.List(actorMembers)) .WithBaseList(SyntaxFactory.BaseList( SyntaxFactory.Token(SyntaxKind.ColonToken), diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 3515bc8b0..13351d193 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -877,4 +877,120 @@ public interface ITestActor await test.RunAsync(); } + + [Fact] + public async Task TestGenericWithConstraints() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + public interface ITrait + { + string GetName(); + } + + [GenerateActorClient] + public interface ITestActor where TTrait : ITrait + { + Task SetTrait(TTrait trait); + Task GetTrait(string name); + } +}"; + + var generatedSource = @"// +#nullable enable +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor where TTrait : Test.ITrait + { + 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 GetTrait(string name) + { + return this.actorProxy.InvokeMethodAsync(""GetTrait"", name); + } + + public System.Threading.Tasks.Task SetTrait(TTrait trait) + { + return this.actorProxy.InvokeMethodAsync(""SetTrait"", trait); + } + } +}"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestGenericWithMultipleTypeParametersAndConstraints() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + public interface ITrait + { + string GetName(); + } + + public interface IValidator + { + bool Validate(T item); + } + + [GenerateActorClient] + public interface ITestActor + where TTrait : ITrait, new() + where TValidator : class, IValidator + { + Task SetTrait(TTrait trait); + Task GetValidatedTrait(string name); + } +}"; + + var generatedSource = @"// +#nullable enable +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor where TTrait : Test.ITrait, new() + where TValidator : class, Test.IValidator + { + 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 GetValidatedTrait(string name) + { + return this.actorProxy.InvokeMethodAsync(""GetValidatedTrait"", name); + } + + public System.Threading.Tasks.Task SetTrait(TTrait trait) + { + return this.actorProxy.InvokeMethodAsync(""SetTrait"", trait); + } + } +}"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } } From 435933cc2a3d805b7074d7702924f9c6e7edbaf1 Mon Sep 17 00:00:00 2001 From: Oisin Grehan Date: Mon, 12 May 2025 14:04:02 -0400 Subject: [PATCH 2/3] update actor generic if to use contravariant type arg Signed-off-by: Oisin Grehan --- .../ActorClientGeneratorTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 13351d193..2841f7b68 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -893,10 +893,10 @@ public interface ITrait } [GenerateActorClient] - public interface ITestActor where TTrait : ITrait + public interface ITestActor where TTrait : ITrait { Task SetTrait(TTrait trait); - Task GetTrait(string name); + Task GetTrait(string name); } }"; @@ -917,9 +917,9 @@ public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) this.actorProxy = actorProxy; } - public System.Threading.Tasks.Task GetTrait(string name) + public System.Threading.Tasks.Task GetTrait(string name) { - return this.actorProxy.InvokeMethodAsync(""GetTrait"", name); + return this.actorProxy.InvokeMethodAsync(""GetTrait"", name); } public System.Threading.Tasks.Task SetTrait(TTrait trait) From 95d92b375e209f915901f6d17e7b7edfeadd5831 Mon Sep 17 00:00:00 2001 From: Oisin Grehan Date: Thu, 7 Aug 2025 14:37:01 -0400 Subject: [PATCH 3/3] Update src/Dapr.Actors.Generators/ActorClientGenerator.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Oisin Grehan --- src/Dapr.Actors.Generators/ActorClientGenerator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index ceb0efd1a..282dc682b 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -184,6 +184,9 @@ private static void GenerateActorClientCode(SourceProductionContext context, Act constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint)); } else if (typeParam.HasValueTypeConstraint) + { + constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint)); + else if (typeParam.HasValueTypeConstraint && !typeParam.HasUnmanagedTypeConstraint) { constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint)); }