Skip to content

Commit 6de068b

Browse files
authored
Merge pull request #60 from koenbeuk/issue-54
Add support projectable members declared on generic abstract classes
2 parents 1771c41 + 682aa4f commit 6de068b

File tree

71 files changed

+649
-717
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+649
-717
lines changed

src/EntityFrameworkCore.Projectables.Generator/DeclarationSyntaxRewriter.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public DeclarationSyntaxRewriter(SemanticModel semanticModel)
2828
visitedNode = visitedParameterSyntax.WithModifiers(node.Modifiers.RemoveAt(thisKeywordIndex));
2929
}
3030

31-
// Strip the parans keyword of any parameter
31+
// Strip the params keyword of any parameter
3232
var paramsKeywordIndex = ((ParameterSyntax)visitedNode).Modifiers.IndexOf(SyntaxKind.ParamsKeyword);
3333
if (paramsKeywordIndex != -1)
3434
{
@@ -73,6 +73,19 @@ public DeclarationSyntaxRewriter(SemanticModel semanticModel)
7373
return base.VisitIdentifierName(node);
7474
}
7575

76+
public override SyntaxNode? VisitGenericName(GenericNameSyntax node)
77+
{
78+
var typeInfo = _semanticModel.GetTypeInfo(node);
79+
if (typeInfo.Type is not null)
80+
{
81+
return SyntaxFactory.ParseTypeName(
82+
typeInfo.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
83+
).WithTriviaFrom(node);
84+
}
85+
86+
return base.VisitGenericName(node);
87+
}
88+
7689
public override SyntaxNode? VisitQualifiedName(QualifiedNameSyntax node)
7790
{
7891
var typeInfo = _semanticModel.GetTypeInfo(node);

src/EntityFrameworkCore.Projectables.Generator/EntityFrameworkCore.Projectables.Generator.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFrameworks>netstandard2.0</TargetFrameworks>
55
<NoWarn>$(NoWarn);NU5128</NoWarn>
66
<IsPackable>false</IsPackable>
77
</PropertyGroup>

src/EntityFrameworkCore.Projectables.Generator/ProjectableDescriptor.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ namespace EntityFrameworkCore.Projectables.Generator
1111
{
1212
public class ProjectableDescriptor
1313
{
14-
public IEnumerable<string>? UsingDirectives { get; set; }
15-
1614
public string? ClassNamespace { get; set; }
1715

1816
public IEnumerable<string>? NestedInClassNames { get; set; }
@@ -23,6 +21,10 @@ public class ProjectableDescriptor
2321

2422
public string? ClassName { get; set; }
2523

24+
public TypeParameterListSyntax? ClassTypeParameterList { get; set; }
25+
26+
public SyntaxList<TypeParameterConstraintClauseSyntax>? ClassConstraintClauses { get; set; }
27+
2628
public string? MemberName { get; set; }
2729

2830
public string? ReturnTypeName { get; set; }
@@ -31,8 +33,8 @@ public class ProjectableDescriptor
3133

3234
public TypeParameterListSyntax? TypeParameterList { get; set; }
3335

34-
public IEnumerable<TypeParameterConstraintClauseSyntax>? ConstraintClauses { get; set; }
36+
public SyntaxList<TypeParameterConstraintClauseSyntax>? ConstraintClauses { get; set; }
3537

36-
public SyntaxNode? Body { get; set; }
38+
public ExpressionSyntax? ExpressionBody { get; set; }
3739
}
3840
}

src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs

+47-21
Original file line numberDiff line numberDiff line change
@@ -123,23 +123,52 @@ x is IPropertySymbol xProperty &&
123123
ClassNamespace = memberSymbol.ContainingType.ContainingNamespace.IsGlobalNamespace ? null : memberSymbol.ContainingType.ContainingNamespace.ToDisplayString(),
124124
MemberName = memberSymbol.Name,
125125
NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType),
126-
ParametersList = SyntaxFactory.ParameterList(),
127-
TypeParameterList = SyntaxFactory.TypeParameterList()
126+
ParametersList = SyntaxFactory.ParameterList()
128127
};
129128

129+
if (memberSymbol.ContainingType is INamedTypeSymbol { IsGenericType: true } containingNamedType)
130+
{
131+
descriptor.ClassTypeParameterList = SyntaxFactory.TypeParameterList();
132+
133+
foreach (var additionalClassTypeParameter in containingNamedType.TypeParameters)
134+
{
135+
descriptor.ClassTypeParameterList = descriptor.ClassTypeParameterList.AddParameters(
136+
SyntaxFactory.TypeParameter(additionalClassTypeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
137+
);
138+
139+
if (!additionalClassTypeParameter.ConstraintTypes.IsDefaultOrEmpty)
140+
{
141+
descriptor.ClassConstraintClauses ??= SyntaxFactory.List<TypeParameterConstraintClauseSyntax>();
142+
143+
descriptor.ClassConstraintClauses = descriptor.ClassConstraintClauses.Value.Add(
144+
SyntaxFactory.TypeParameterConstraintClause(
145+
SyntaxFactory.IdentifierName(additionalClassTypeParameter.Name),
146+
SyntaxFactory.SeparatedList<TypeParameterConstraintSyntax>(
147+
additionalClassTypeParameter
148+
.ConstraintTypes
149+
.Select(c => SyntaxFactory.TypeConstraint(
150+
SyntaxFactory.IdentifierName(c.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
151+
))
152+
)
153+
)
154+
);
155+
}
156+
157+
// todo: add additional type constraints
158+
}
159+
}
160+
130161
if (!member.Modifiers.Any(SyntaxKind.StaticKeyword))
131162
{
132163
descriptor.ParametersList = descriptor.ParametersList.AddParameters(
133164
SyntaxFactory.Parameter(
134-
SyntaxFactory.Identifier("@this")
135-
).WithType(
136-
SyntaxFactory.ParseTypeName(
137-
memberSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
138-
)
139-
.WithTrailingTrivia(
140-
SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ")
141-
)
165+
SyntaxFactory.Identifier("@this")
166+
)
167+
.WithType(
168+
SyntaxFactory.ParseTypeName(
169+
memberSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
142170
)
171+
)
143172
);
144173
}
145174

@@ -169,14 +198,15 @@ x is IPropertySymbol xProperty &&
169198
var returnType = declarationSyntaxRewriter.Visit(methodDeclarationSyntax.ReturnType);
170199

171200
descriptor.ReturnTypeName = returnType.ToString();
172-
descriptor.Body = expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression);
201+
descriptor.ExpressionBody = (ExpressionSyntax)expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression);
173202
foreach (var additionalParameter in ((ParameterListSyntax)declarationSyntaxRewriter.Visit(methodDeclarationSyntax.ParameterList)).Parameters)
174203
{
175204
descriptor.ParametersList = descriptor.ParametersList.AddParameters(additionalParameter);
176205
}
177206

178207
if (methodDeclarationSyntax.TypeParameterList is not null)
179208
{
209+
descriptor.TypeParameterList = SyntaxFactory.TypeParameterList();
180210
foreach (var additionalTypeParameter in ((TypeParameterListSyntax)declarationSyntaxRewriter.Visit(methodDeclarationSyntax.TypeParameterList)).Parameters)
181211
{
182212
descriptor.TypeParameterList = descriptor.TypeParameterList.AddParameters(additionalTypeParameter);
@@ -185,8 +215,11 @@ x is IPropertySymbol xProperty &&
185215

186216
if (methodDeclarationSyntax.ConstraintClauses.Any())
187217
{
188-
descriptor.ConstraintClauses = methodDeclarationSyntax.ConstraintClauses
189-
.Select(x => (TypeParameterConstraintClauseSyntax)declarationSyntaxRewriter.Visit(x));
218+
descriptor.ConstraintClauses = SyntaxFactory.List(
219+
methodDeclarationSyntax
220+
.ConstraintClauses
221+
.Select(x => (TypeParameterConstraintClauseSyntax)declarationSyntaxRewriter.Visit(x))
222+
);
190223
}
191224
}
192225
else if (memberBody is PropertyDeclarationSyntax propertyDeclarationSyntax)
@@ -201,20 +234,13 @@ x is IPropertySymbol xProperty &&
201234
var returnType = declarationSyntaxRewriter.Visit(propertyDeclarationSyntax.Type);
202235

203236
descriptor.ReturnTypeName = returnType.ToString();
204-
descriptor.Body = expressionSyntaxRewriter.Visit(propertyDeclarationSyntax.ExpressionBody.Expression);
237+
descriptor.ExpressionBody = (ExpressionSyntax)expressionSyntaxRewriter.Visit(propertyDeclarationSyntax.ExpressionBody.Expression);
205238
}
206239
else
207240
{
208241
return null;
209242
}
210243

211-
descriptor.UsingDirectives =
212-
member.SyntaxTree
213-
.GetRoot()
214-
.DescendantNodes()
215-
.OfType<UsingDirectiveSyntax>()
216-
.Select(x => x.ToString());
217-
218244
return descriptor;
219245
}
220246
}

src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs

+85-59
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
using System.Collections.Immutable;
99
using System.Diagnostics;
1010
using System.Linq;
11+
using System.Security.Cryptography.X509Certificates;
1112
using System.Text;
1213
using System.Threading;
1314
using System.Threading.Tasks;
15+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1416

1517
namespace EntityFrameworkCore.Projectables.Generator
1618
{
@@ -19,6 +21,22 @@ public class ProjectionExpressionGenerator : IIncrementalGenerator
1921
{
2022
private const string ProjectablesAttributeName = "EntityFrameworkCore.Projectables.ProjectableAttribute";
2123

24+
static readonly AttributeSyntax _editorBrowsableAttribute =
25+
Attribute(
26+
ParseName("global::System.ComponentModel.EditorBrowsable"),
27+
AttributeArgumentList(
28+
SingletonSeparatedList(
29+
AttributeArgument(
30+
MemberAccessExpression(
31+
SyntaxKind.SimpleMemberAccessExpression,
32+
IdentifierName("global::System.ComponentModel.EditorBrowsableState"),
33+
IdentifierName("Never")
34+
)
35+
)
36+
)
37+
)
38+
);
39+
2240
public void Initialize(IncrementalGeneratorInitializationContext context)
2341
{
2442
// Do a simple filter for members
@@ -91,76 +109,84 @@ static void Execute(Compilation compilation, ImmutableArray<MemberDeclarationSyn
91109
throw new InvalidOperationException("Expected a memberName here");
92110
}
93111

94-
resultBuilder.Clear();
95-
96-
resultBuilder.AppendLine("// <auto-generated/>");
97-
98-
if (projectable.UsingDirectives is not null)
99-
{
100-
foreach (var usingDirective in projectable.UsingDirectives.Distinct())
101-
{
102-
resultBuilder.AppendLine(usingDirective);
103-
}
104-
}
105-
106-
if (projectable.TargetClassNamespace is not null)
107-
{
108-
var targetClassUsingDirective = $"using {projectable.TargetClassNamespace};";
109-
110-
if (!projectable.UsingDirectives.Contains(targetClassUsingDirective))
111-
{
112-
resultBuilder.AppendLine(targetClassUsingDirective);
113-
}
114-
}
112+
var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName);
113+
var generatedFileName = projectable.ClassTypeParameterList is not null ? $"{generatedClassName}-{projectable.ClassTypeParameterList.ChildNodes().Count()}.g.cs" : $"{generatedClassName}.g.cs";
114+
115+
var classSyntax = ClassDeclaration(generatedClassName)
116+
.WithModifiers(TokenList(Token(SyntaxKind.StaticKeyword)))
117+
.WithTypeParameterList(projectable.ClassTypeParameterList)
118+
.WithConstraintClauses(projectable.ClassConstraintClauses ?? List<TypeParameterConstraintClauseSyntax>())
119+
.AddAttributeLists(
120+
AttributeList()
121+
.AddAttributes(_editorBrowsableAttribute)
122+
)
123+
.AddMembers(
124+
MethodDeclaration(
125+
GenericName(
126+
Identifier("global::System.Linq.Expressions.Expression"),
127+
TypeArgumentList(
128+
SingletonSeparatedList(
129+
(TypeSyntax)GenericName(
130+
Identifier("global::System.Func"),
131+
GetLambdaTypeArgumentListSyntax(projectable)
132+
)
133+
)
134+
)
135+
),
136+
"Expression"
137+
)
138+
.WithModifiers(TokenList(Token(SyntaxKind.StaticKeyword)))
139+
.WithTypeParameterList(projectable.TypeParameterList)
140+
.WithConstraintClauses(projectable.ConstraintClauses ?? List<TypeParameterConstraintClauseSyntax>())
141+
.WithBody(
142+
Block(
143+
ReturnStatement(
144+
ParenthesizedLambdaExpression(
145+
projectable.ParametersList ?? ParameterList(),
146+
null,
147+
projectable.ExpressionBody
148+
)
149+
)
150+
)
151+
)
152+
);
115153

116-
if (projectable.ClassNamespace is not null && projectable.ClassNamespace != projectable.TargetClassNamespace)
117-
{
118-
var classUsingDirective = $"using {projectable.ClassNamespace};";
154+
#nullable disable
119155

120-
if (!projectable.UsingDirectives.Contains(classUsingDirective))
121-
{
122-
resultBuilder.AppendLine(classUsingDirective);
123-
}
124-
}
156+
var compilationUnit = CompilationUnit()
157+
.AddMembers(
158+
NamespaceDeclaration(
159+
ParseName("EntityFrameworkCore.Projectables.Generated")
160+
).AddMembers(classSyntax)
161+
)
162+
.WithLeadingTrivia(
163+
TriviaList(
164+
Comment("// <auto-generated/>"),
165+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true))
166+
)
167+
);
125168

126-
var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName);
127169

128-
var lambdaTypeArguments = SyntaxFactory.TypeArgumentList(
129-
SyntaxFactory.SeparatedList(
130-
projectable.ParametersList?.Parameters.Where(p => p.Type is not null).Select(p => p.Type!)
131-
)
132-
);
170+
context.AddSource(generatedFileName, SourceText.From(compilationUnit.NormalizeWhitespace().ToFullString(), Encoding.UTF8));
133171

134-
resultBuilder.Append($@"
135-
namespace EntityFrameworkCore.Projectables.Generated
136-
#nullable disable
137-
{{
138-
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
139-
public static class {generatedClassName}
140-
{{
141-
public static System.Linq.Expressions.Expression<System.Func<{(lambdaTypeArguments.Arguments.Any() ? $"{lambdaTypeArguments.Arguments}, " : "")}{projectable.ReturnTypeName}>> Expression{(projectable.TypeParameterList?.Parameters.Any() == true ? projectable.TypeParameterList.ToString() : string.Empty)}()");
142172

143-
if (projectable.ConstraintClauses is not null)
173+
static TypeArgumentListSyntax GetLambdaTypeArgumentListSyntax(ProjectableDescriptor projectable)
144174
{
145-
foreach (var constraintClause in projectable.ConstraintClauses)
175+
var lambdaTypeArguments = TypeArgumentList(
176+
SeparatedList(
177+
// TODO: Document where clause
178+
projectable.ParametersList?.Parameters.Where(p => p.Type is not null).Select(p => p.Type!)
179+
)
180+
);
181+
182+
if (projectable.ReturnTypeName is not null)
146183
{
147-
resultBuilder.Append($@"
148-
{constraintClause}");
184+
lambdaTypeArguments = lambdaTypeArguments.AddArguments(ParseTypeName(projectable.ReturnTypeName));
149185
}
150-
}
151-
152-
resultBuilder.Append($@"
153-
{{
154-
return {projectable.ParametersList} =>
155-
{projectable.Body};
156-
}}
157-
}}
158-
}}");
159186

160-
161-
context.AddSource($"{generatedClassName}.g.cs", SourceText.From(resultBuilder.ToString(), Encoding.UTF8));
187+
return lambdaTypeArguments;
188+
}
162189
}
163190
}
164-
165191
}
166192
}

src/EntityFrameworkCore.Projectables/EntityFrameworkCore.Projectables.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>$(TargetFrameworkVersion)</TargetFramework>
4+
<TargetFramework>net6.0</TargetFramework>
55
<PackageReadmeFile>README.md</PackageReadmeFile>
66
</PropertyGroup>
77

src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs

+13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ namespace EntityFrameworkCore.Projectables.Extensions
1212
{
1313
public static class TypeExtensions
1414
{
15+
public static string GetSimplifiedTypeName(this Type type)
16+
{
17+
var name = type.Name;
18+
19+
var backtickIndex = name.IndexOf("`");
20+
if (backtickIndex != -1)
21+
{
22+
name = name.Substring(0, backtickIndex);
23+
}
24+
25+
return name;
26+
}
27+
1528
public static IEnumerable<Type> GetNestedTypePath(this Type type)
1629
{
1730
if (type.IsNested && type.DeclaringType is not null)

0 commit comments

Comments
 (0)