Add support for notnull, struct, class and new() generic constraints

This commit is contained in:
fabien.menager
2023-08-25 14:03:06 +02:00
parent b458c715c8
commit 02c9ce7b58
6 changed files with 142 additions and 13 deletions

View File

@@ -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<TypeParameterConstraintClauseSyntax>();
var parameters = new List<TypeConstraintSyntax>();
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<TypeParameterConstraintSyntax>(
additionalClassTypeParameter
.ConstraintTypes
.Select(c => SyntaxFactory.TypeConstraint(
SyntaxFactory.IdentifierName(c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
))
)
SyntaxFactory.SeparatedList<TypeParameterConstraintSyntax>(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));
}
}

View File

@@ -111,4 +111,4 @@ namespace EntityFrameworkCore.Projectables.Services
return base.VisitMember(node);
}
}
}
}

View File

@@ -0,0 +1,20 @@
// <auto-generated/>
#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<T, TEnum>
where T : global::Foo.TypedObject<TEnum> where TEnum : struct, global::System.Enum
{
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity<T, TEnum>, string>> Expression()
{
return (global::Foo.Entity<T, TEnum> @this) => $"{@this.FirstName} {@this.LastName} {@this.SomeSubobject.SomeProp}";
}
}
}

View File

@@ -0,0 +1,19 @@
// <auto-generated/>
#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<T>
{
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity<T>, string>> Expression()
{
return (global::Foo.Entity<T> @this) => $"{@this.FirstName} {@this.LastName} {@this.SomeSubobject}";
}
}
}

View File

@@ -7,11 +7,11 @@ namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static class _EntityBase_GetId<TId>
where TId : global::System.ICloneable
where TId : global::System.ICloneable, new()
{
static global::System.Linq.Expressions.Expression<global::System.Func<TId>> Expression()
{
return () => default;
}
}
}
}

View File

@@ -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<TEnum> where TEnum : struct, System.Enum
{
public TEnum SomeProp { get; set; }
}
public abstract class Entity<T, TEnum>where T : TypedObject<TEnum> 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<T> 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()
{