Skip to content

Commit 7ef4e64

Browse files
authored
Merge pull request #83 from am-creations/fix-generic-constraints
Add support for notnull, struct, class and new() generic constraints
2 parents b458c71 + 4c3085c commit 7ef4e64

File tree

6 files changed

+141
-12
lines changed

6 files changed

+141
-12
lines changed

src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,48 @@ x is IPropertySymbol xProperty &&
137137
descriptor.ClassTypeParameterList = descriptor.ClassTypeParameterList.AddParameters(
138138
SyntaxFactory.TypeParameter(additionalClassTypeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
139139
);
140-
140+
141+
// See https://github.com/dotnet/roslyn/blob/d7e010bbe5b1d37837417fc5e79ecb2fd9b7b487/src/VisualStudio/CSharp/Impl/ObjectBrowser/DescriptionBuilder.cs#L340
141142
if (!additionalClassTypeParameter.ConstraintTypes.IsDefaultOrEmpty)
142143
{
143144
descriptor.ClassConstraintClauses ??= SyntaxFactory.List<TypeParameterConstraintClauseSyntax>();
144145

146+
var parameters = new List<TypeConstraintSyntax>();
147+
148+
if (additionalClassTypeParameter.HasReferenceTypeConstraint)
149+
{
150+
parameters.Add(MakeTypeConstraint("class"));
151+
}
152+
153+
if (additionalClassTypeParameter.HasValueTypeConstraint)
154+
{
155+
parameters.Add(MakeTypeConstraint("struct"));
156+
}
157+
158+
if (additionalClassTypeParameter.HasNotNullConstraint)
159+
{
160+
parameters.Add(MakeTypeConstraint("notnull"));
161+
}
162+
163+
parameters.AddRange(additionalClassTypeParameter
164+
.ConstraintTypes
165+
.Select(c =>
166+
MakeTypeConstraint(c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
167+
)
168+
);
169+
170+
if (additionalClassTypeParameter.HasConstructorConstraint)
171+
{
172+
parameters.Add(MakeTypeConstraint("new()"));
173+
}
174+
145175
descriptor.ClassConstraintClauses = descriptor.ClassConstraintClauses.Value.Add(
146176
SyntaxFactory.TypeParameterConstraintClause(
147177
SyntaxFactory.IdentifierName(additionalClassTypeParameter.Name),
148-
SyntaxFactory.SeparatedList<TypeParameterConstraintSyntax>(
149-
additionalClassTypeParameter
150-
.ConstraintTypes
151-
.Select(c => SyntaxFactory.TypeConstraint(
152-
SyntaxFactory.IdentifierName(c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
153-
))
154-
)
178+
SyntaxFactory.SeparatedList<TypeParameterConstraintSyntax>(parameters)
155179
)
156180
);
157181
}
158-
159-
// todo: add additional type constraints
160182
}
161183
}
162184

@@ -245,5 +267,7 @@ x is IPropertySymbol xProperty &&
245267

246268
return descriptor;
247269
}
270+
271+
private static TypeConstraintSyntax MakeTypeConstraint(string constraint) => SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName(constraint));
248272
}
249273
}

src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,4 @@ PropertyInfo property when nodeExpression is not null
111111
return base.VisitMember(node);
112112
}
113113
}
114-
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using System;
4+
using System.Linq;
5+
using System.Collections.Generic;
6+
using EntityFrameworkCore.Projectables;
7+
using Foo;
8+
9+
namespace EntityFrameworkCore.Projectables.Generated
10+
{
11+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
12+
static class Foo_Entity_FullName<T, TEnum>
13+
where T : global::Foo.TypedObject<TEnum> where TEnum : struct, global::System.Enum
14+
{
15+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity<T, TEnum>, string>> Expression()
16+
{
17+
return (global::Foo.Entity<T, TEnum> @this) => $"{@this.FirstName} {@this.LastName} {@this.SomeSubobject.SomeProp}";
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using System;
4+
using System.Linq;
5+
using System.Collections.Generic;
6+
using EntityFrameworkCore.Projectables;
7+
using Foo;
8+
9+
namespace EntityFrameworkCore.Projectables.Generated
10+
{
11+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
12+
static class Foo_Entity_FullName<T>
13+
{
14+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity<T>, string>> Expression()
15+
{
16+
return (global::Foo.Entity<T> @this) => $"{@this.FirstName} {@this.LastName} {@this.SomeSubobject}";
17+
}
18+
}
19+
}

tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericTypesWithConstraints.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace EntityFrameworkCore.Projectables.Generated
77
{
88
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
99
static class _EntityBase_GetId<TId>
10-
where TId : global::System.ICloneable
10+
where TId : global::System.ICloneable, new()
1111
{
1212
static global::System.Linq.Expressions.Expression<global::System.Func<TId>> Expression()
1313
{

tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,72 @@ public static string EnforceString<T>(T value) where T : unmanaged
921921
return Verifier.Verify(result.GeneratedTrees[0].ToString());
922922
}
923923

924+
[Fact]
925+
public Task GenericClassesWithContraints_AreRewritten()
926+
{
927+
var compilation = CreateCompilation(@"
928+
using System;
929+
using System.Linq;
930+
using System.Collections.Generic;
931+
using EntityFrameworkCore.Projectables;
932+
933+
namespace Foo {
934+
public class TypedObject<TEnum> where TEnum : struct, System.Enum
935+
{
936+
public TEnum SomeProp { get; set; }
937+
}
938+
939+
public abstract class Entity<T, TEnum>where T : TypedObject<TEnum> where TEnum : struct, System.Enum
940+
{
941+
public int Id { get; set; }
942+
public string FirstName { get; set; }
943+
public string LastName { get; set; }
944+
public T SomeSubobject { get; set; }
945+
946+
[Projectable]
947+
public string FullName => $""{FirstName} {LastName} {SomeSubobject.SomeProp}"";
948+
}
949+
}
950+
");
951+
var result = RunGenerator(compilation);
952+
953+
Assert.Empty(result.Diagnostics);
954+
Assert.Single(result.GeneratedTrees);
955+
956+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
957+
}
958+
959+
[Fact]
960+
public Task GenericClassesWithTypeContraints_AreRewritten()
961+
{
962+
var compilation = CreateCompilation(@"
963+
using System;
964+
using System.Linq;
965+
using System.Collections.Generic;
966+
using EntityFrameworkCore.Projectables;
967+
968+
namespace Foo {
969+
public abstract class Entity<T> where T : notnull
970+
{
971+
public int Id { get; set; }
972+
public string FirstName { get; set; }
973+
public string LastName { get; set; }
974+
public T SomeSubobject { get; set; }
975+
976+
[Projectable]
977+
public string FullName => $""{FirstName} {LastName} {SomeSubobject}"";
978+
}
979+
}
980+
");
981+
982+
var result = RunGenerator(compilation);
983+
984+
Assert.Empty(result.Diagnostics);
985+
// Assert.Single(result.GeneratedTrees);
986+
987+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
988+
}
989+
924990
[Fact]
925991
public Task DeclarationTypeNamesAreGettingFullyQualified()
926992
{

0 commit comments

Comments
 (0)