From 02c9ce7b58ce206119c8769e2cb745813f31aa38 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Fri, 25 Aug 2023 14:03:06 +0200 Subject: [PATCH] Add support for notnull, struct, class and new() generic constraints --- .../ProjectableInterpreter.cs | 44 ++++++++++--- .../Services/ProjectableExpressionReplacer.cs | 2 +- ...esWithContraints_AreRewritten.verified.txt | 20 ++++++ ...thTypeContraints_AreRewritten.verified.txt | 19 ++++++ ...s.GenericTypesWithConstraints.verified.txt | 4 +- .../ProjectionExpressionGeneratorTests.cs | 66 +++++++++++++++++++ 6 files changed, 142 insertions(+), 13 deletions(-) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithContraints_AreRewritten.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithTypeContraints_AreRewritten.verified.txt diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs index 216a499..339b46b 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs @@ -137,26 +137,48 @@ namespace EntityFrameworkCore.Projectables.Generator descriptor.ClassTypeParameterList = descriptor.ClassTypeParameterList.AddParameters( SyntaxFactory.TypeParameter(additionalClassTypeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) ); - + + // See https://github.com/dotnet/roslyn/blob/d7e010bbe5b1d37837417fc5e79ecb2fd9b7b487/src/VisualStudio/CSharp/Impl/ObjectBrowser/DescriptionBuilder.cs#L340 if (!additionalClassTypeParameter.ConstraintTypes.IsDefaultOrEmpty) { descriptor.ClassConstraintClauses ??= SyntaxFactory.List(); + var parameters = new List(); + + if (additionalClassTypeParameter.HasReferenceTypeConstraint) + { + parameters.Add(MakeTypeConstraint("class")); + } + + if (additionalClassTypeParameter.HasValueTypeConstraint) + { + parameters.Add(MakeTypeConstraint("struct")); + } + + if (additionalClassTypeParameter.HasNotNullConstraint) + { + parameters.Add(MakeTypeConstraint("notnull")); + } + + parameters.AddRange(additionalClassTypeParameter + .ConstraintTypes + .Select(c => + MakeTypeConstraint(c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + ) + ); + + if (additionalClassTypeParameter.HasConstructorConstraint) + { + parameters.Add(MakeTypeConstraint("new()")); + } + descriptor.ClassConstraintClauses = descriptor.ClassConstraintClauses.Value.Add( SyntaxFactory.TypeParameterConstraintClause( SyntaxFactory.IdentifierName(additionalClassTypeParameter.Name), - SyntaxFactory.SeparatedList( - additionalClassTypeParameter - .ConstraintTypes - .Select(c => SyntaxFactory.TypeConstraint( - SyntaxFactory.IdentifierName(c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) - )) - ) + SyntaxFactory.SeparatedList(parameters) ) ); } - - // todo: add additional type constraints } } @@ -245,5 +267,7 @@ namespace EntityFrameworkCore.Projectables.Generator return descriptor; } + + private static TypeConstraintSyntax MakeTypeConstraint(string constraint) => SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName(constraint)); } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index d304b29..8cfb703 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -111,4 +111,4 @@ namespace EntityFrameworkCore.Projectables.Services return base.VisitMember(node); } } -} \ No newline at end of file +} diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithContraints_AreRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithContraints_AreRewritten.verified.txt new file mode 100644 index 0000000..df29739 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithContraints_AreRewritten.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using System; +using System.Linq; +using System.Collections.Generic; +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Entity_FullName + where T : global::Foo.TypedObject where TEnum : struct, global::System.Enum + { + static global::System.Linq.Expressions.Expression, string>> Expression() + { + return (global::Foo.Entity @this) => $"{@this.FirstName} {@this.LastName} {@this.SomeSubobject.SomeProp}"; + } + } +} diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithTypeContraints_AreRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithTypeContraints_AreRewritten.verified.txt new file mode 100644 index 0000000..049cc0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericClassesWithTypeContraints_AreRewritten.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using System; +using System.Linq; +using System.Collections.Generic; +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Entity_FullName + { + static global::System.Linq.Expressions.Expression, string>> Expression() + { + return (global::Foo.Entity @this) => $"{@this.FirstName} {@this.LastName} {@this.SomeSubobject}"; + } + } +} diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericTypesWithConstraints.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericTypesWithConstraints.verified.txt index 468e6ad..737d5b5 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericTypesWithConstraints.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericTypesWithConstraints.verified.txt @@ -7,11 +7,11 @@ namespace EntityFrameworkCore.Projectables.Generated { [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class _EntityBase_GetId - where TId : global::System.ICloneable + where TId : global::System.ICloneable, new() { static global::System.Linq.Expressions.Expression> Expression() { return () => default; } } -} \ No newline at end of file +} diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index 36981f2..11cfe89 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -921,6 +921,72 @@ namespace Foo { return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + [Fact] + public Task GenericClassesWithContraints_AreRewritten() + { + var compilation = CreateCompilation(@" +using System; +using System.Linq; +using System.Collections.Generic; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public class TypedObject where TEnum : struct, System.Enum + { + public TEnum SomeProp { get; set; } + } + + public abstract class Entitywhere T : TypedObject where TEnum : struct, System.Enum + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public T SomeSubobject { get; set; } + + [Projectable] + public string FullName => $""{FirstName} {LastName} {SomeSubobject.SomeProp}""; + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task GenericClassesWithTypeContraints_AreRewritten() + { + var compilation = CreateCompilation(@" +using System; +using System.Linq; +using System.Collections.Generic; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public abstract class Entity where T : notnull + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public T SomeSubobject { get; set; } + + [Projectable] + public string FullName => $""{FirstName} {LastName} {SomeSubobject}""; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + // Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + [Fact] public Task DeclarationTypeNamesAreGettingFullyQualified() {