Skip to content

Commit 4d23623

Browse files
committed
Added a lot more support for generic type parameters
1 parent 1631966 commit 4d23623

18 files changed

+429
-61
lines changed

src/PublicInterfaceGenerator/GeneratorParsers/ClassParser.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public static class ClassParser
7272
var nameSpace = namespaceName ?? (symbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : symbol.ContainingNamespace.ToString());
7373
var extraInterfaces = interfacesNames?.Trim() ?? string.Empty;
7474

75+
var interfaceGenericParameters = GenericsParser.ParseGenericParameters(symbol.TypeParameters);
76+
7577
var methodsBuilder = ImmutableArray.CreateBuilder<InterfaceToGenerateInfo.Method>();
7678
var propertiesBuilder = ImmutableArray.CreateBuilder<InterfaceToGenerateInfo.Property>();
7779
var eventsBuilder = ImmutableArray.CreateBuilder<InterfaceToGenerateInfo.Event>();
@@ -111,6 +113,7 @@ public static class ClassParser
111113
FullNamespace: nameSpace,
112114
Interfaces: extraInterfaces,
113115
InheritsFromIDisposable: inheritsFromIDisposable,
116+
GenericParameters: interfaceGenericParameters,
114117
Methods: methodsBuilder.ToImmutableArray(),
115118
Properties: propertiesBuilder.ToImmutableArray(),
116119
Events: eventsBuilder.ToImmutableArray());
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Text;
5+
6+
using Microsoft.CodeAnalysis;
7+
8+
namespace ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.GeneratorParsers;
9+
10+
public static class GenericsParser
11+
{
12+
public static ImmutableArray<InterfaceToGenerateInfo.GenericParameter> ParseGenericParameters(ImmutableArray<ITypeParameterSymbol> typeParameters)
13+
{
14+
var builder = ImmutableArray.CreateBuilder<InterfaceToGenerateInfo.GenericParameter>();
15+
foreach (var typeParameter in typeParameters)
16+
{
17+
var genericConstraintsBuilder = new List<string>();
18+
genericConstraintsBuilder.Clear();
19+
20+
if (typeParameter.HasReferenceTypeConstraint)
21+
{
22+
if (typeParameter.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated)
23+
{
24+
genericConstraintsBuilder.Add("class?");
25+
}
26+
else
27+
{
28+
genericConstraintsBuilder.Add("class");
29+
}
30+
}
31+
32+
if (typeParameter.HasUnmanagedTypeConstraint)
33+
{
34+
genericConstraintsBuilder.Add("unmanaged");
35+
}
36+
else if (typeParameter.HasValueTypeConstraint)
37+
{
38+
genericConstraintsBuilder.Add("struct");
39+
}
40+
41+
if (typeParameter.HasNotNullConstraint)
42+
{
43+
genericConstraintsBuilder.Add("notnull");
44+
}
45+
46+
if (typeParameter.HasConstructorConstraint)
47+
{
48+
genericConstraintsBuilder.Add("new()");
49+
}
50+
51+
foreach (var constraintType in typeParameter.ConstraintTypes)
52+
{
53+
var constraintTypeName = constraintType.ToString();
54+
if (constraintType is ITypeParameterSymbol constraintTypeParameter)
55+
{
56+
constraintTypeName = constraintTypeParameter.Name;
57+
}
58+
59+
genericConstraintsBuilder.Add(constraintTypeName);
60+
}
61+
62+
var constraintTypes = string.Join(", ", genericConstraintsBuilder);
63+
64+
var genericParameter = new InterfaceToGenerateInfo.GenericParameter(typeParameter.Ordinal, typeParameter.Name, typeParameter.NullableAnnotation, constraintTypes);
65+
builder.Add(genericParameter);
66+
}
67+
68+
return builder.ToImmutableArray();
69+
}
70+
71+
}

src/PublicInterfaceGenerator/GeneratorParsers/MethodParser.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,7 @@ public static class MethodParser
3434
returnType = symbol.ReturnType.ToString();
3535
}
3636

37-
var genericParameters = symbol.TypeParameters
38-
.Select(x =>
39-
{
40-
var constraintTypes = string.Join(", ", x.ConstraintTypes.Select(x => x.Name));
41-
return new InterfaceToGenerateInfo.MethodGenericParameter(x.Ordinal, x.Name, x.NullableAnnotation, constraintTypes);
42-
}).ToImmutableArray();
37+
var genericParameters = GenericsParser.ParseGenericParameters(symbol.TypeParameters);
4338

4439
var argumentBuilder = ImmutableArray.CreateBuilder<InterfaceToGenerateInfo.MethodArgument>();
4540

@@ -48,14 +43,14 @@ public static class MethodParser
4843
var argName = methodParameter.Name;
4944
var dataType = methodParameter.Type.ToDisplayString();
5045
var nullableAnnotation = methodParameter.NullableAnnotation;
51-
5246
var interfaceArgument = new InterfaceToGenerateInfo.MethodArgument(argName, dataType, nullableAnnotation);
5347
argumentBuilder.Add(interfaceArgument);
5448
}
5549

5650
return new InterfaceToGenerateInfo.Method(methodName, returnType, argumentBuilder.ToImmutableArray(), genericParameters, methodComments);
5751
}
5852

53+
5954
private static bool IsSymbolValid(IMethodSymbol symbol, string extraClassInterfaces, bool inheritsFromIDisposable)
6055
{
6156
if (string.Equals(".ctor", symbol.Name, StringComparison.Ordinal))

src/PublicInterfaceGenerator/InterfaceToGenerateInfo.cs

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.Text;
5-
using System.Xml.Linq;
65

76
using Microsoft.CodeAnalysis;
87

@@ -15,10 +14,65 @@ public record InterfaceToGenerateInfo(
1514
string FullNamespace,
1615
string Interfaces,
1716
bool InheritsFromIDisposable,
17+
ImmutableArray<GenericParameter> GenericParameters,
1818
ImmutableArray<Method> Methods,
1919
ImmutableArray<Property> Properties,
2020
ImmutableArray<Event> Events)
2121
{
22+
public string GenerateInterfaceDefinitionString()
23+
{
24+
var builder = new StringBuilder();
25+
26+
_ = builder.Append($"public interface {InterfaceName}");
27+
28+
if (GenericParameters.Any())
29+
{
30+
_ = builder.Append('<');
31+
32+
for (int i = 0; i < GenericParameters.Length; i++)
33+
{
34+
var parameter = GenericParameters[i];
35+
_ = builder.Append(parameter.Name);
36+
if (parameter.NullableAnnotation == NullableAnnotation.Annotated)
37+
{
38+
_ = builder.Append('?');
39+
}
40+
41+
//If there are more generic parameters, add a comma to separate items in the list
42+
if (i + 1 < GenericParameters.Length)
43+
{
44+
_ = builder.Append($", ");
45+
}
46+
}
47+
48+
_ = builder.Append('>');
49+
}
50+
51+
if (!string.IsNullOrWhiteSpace(Interfaces))
52+
{
53+
_ = builder.Append($" : {Interfaces}");
54+
if (InheritsFromIDisposable)
55+
{
56+
_ = builder.Append(", System.IDisposable");
57+
}
58+
}
59+
else if (InheritsFromIDisposable)
60+
{
61+
_ = builder.Append(" : System.IDisposable");
62+
}
63+
64+
foreach (var genericParam in GenericParameters)
65+
{
66+
if (!string.IsNullOrWhiteSpace(genericParam.ConstraintTypes))
67+
{
68+
_ = builder.Append($" where {genericParam.Name} : {genericParam.ConstraintTypes}");
69+
}
70+
}
71+
return builder.ToString();
72+
}
73+
74+
public record GenericParameter(int Ordinal, string Name, NullableAnnotation NullableAnnotation, string ConstraintTypes);
75+
2276
internal static string CombineLineWithComments(string comments, string definitionLine)
2377
{
2478
if (string.IsNullOrWhiteSpace(comments))
@@ -31,31 +85,31 @@ internal static string CombineLineWithComments(string comments, string definitio
3185
}
3286
}
3387

34-
public record Method(string Name, string ReturnType, ImmutableArray<MethodArgument> Arguments, ImmutableArray<MethodGenericParameter> MethodGenericParameters, string Comments)
88+
public record Method(string Name, string ReturnType, ImmutableArray<MethodArgument> Arguments, ImmutableArray<GenericParameter> GenericParameters, string Comments)
3589
{
3690
public string ToMethodString()
3791
{
3892
var builder = new StringBuilder();
3993

4094
_ = builder.Append($"{ReturnType} {Name}");
4195

42-
if (MethodGenericParameters.Any())
96+
if (GenericParameters.Any())
4397
{
4498
_ = builder.Append('<');
4599

46-
for (int i = 0; i < MethodGenericParameters.Length; i++)
47-
{
48-
var parameter = MethodGenericParameters[i];
100+
for (int i = 0; i < GenericParameters.Length; i++)
101+
{
102+
var parameter = GenericParameters[i];
49103
_ = builder.Append(parameter.Name);
50104
if (parameter.NullableAnnotation == NullableAnnotation.Annotated)
51-
{
105+
{
52106
_ = builder.Append('?');
53107
}
54108

55109
//If there are more generic parameters, add a comma to separate items in the list
56-
if (i + 1 < MethodGenericParameters.Length)
57-
{
58-
_ = builder.Append($", ");
110+
if (i + 1 < GenericParameters.Length)
111+
{
112+
_ = builder.Append($", ");
59113
}
60114
}
61115

@@ -64,7 +118,7 @@ public string ToMethodString()
64118

65119
_ = builder.Append($"({string.Join(", ", Arguments.Select(a => a.ToArgumentString()))})");
66120

67-
foreach (var genericParam in MethodGenericParameters)
121+
foreach (var genericParam in GenericParameters)
68122
{
69123
if (!string.IsNullOrWhiteSpace(genericParam.ConstraintTypes))
70124
{
@@ -87,14 +141,6 @@ public string ToArgumentString()
87141
}
88142
}
89143

90-
public record MethodGenericParameter(int Ordinal, string Name, NullableAnnotation NullableAnnotation, string ConstraintTypes)
91-
{
92-
public string ToTypeParameter()
93-
{
94-
return $"";
95-
}
96-
}
97-
98144
public record Property(string Name, string ReturnType, bool HasValidGet, bool HasValidSet, string Comments)
99145
{
100146
public string ToPropertyString()

src/PublicInterfaceGenerator/SourceGenerationHelper.cs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,7 @@ public static string GenerateInterface(in InterfaceToGenerateInfo interfaceInfo)
6969
_ = builder.AppendLine($"#nullable enable");
7070
}
7171

72-
var interfaceDefinitionLine = $"public interface {interfaceInfo.InterfaceName}";
73-
if (!string.IsNullOrWhiteSpace(interfaceInfo.Interfaces))
74-
{
75-
interfaceDefinitionLine += $" : {interfaceInfo.Interfaces}";
76-
if (interfaceInfo.InheritsFromIDisposable)
77-
{
78-
interfaceDefinitionLine += ", System.IDisposable";
79-
}
80-
}
81-
else if (interfaceInfo.InheritsFromIDisposable)
82-
{
83-
interfaceDefinitionLine += " : System.IDisposable";
84-
}
72+
var interfaceDefinitionLine = interfaceInfo.GenerateInterfaceDefinitionString();
8573

8674
_ = builder.AppendLine($"namespace {interfaceInfo.FullNamespace};");
8775
_ = builder.AppendLine();

src/UnitTests/InterfaceDefinitionTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma warning disable IDE0058 // Expression value is never used
22

3+
using static UnitTests.InterfaceDefinitionTests;
4+
35
namespace UnitTests;
46

57
public class InterfaceDefinitionTests
@@ -101,5 +103,73 @@ public class MyClass : IMyClass
101103

102104
await TestHelper.VerifyAsync(source, SnapshotsDirectory);
103105
}
106+
107+
[Fact]
108+
public async Task GenericClass()
109+
{
110+
var source = """
111+
using ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.Attributes;
112+
namespace ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.UnitTestClasses;
113+
114+
[GenerateInterfaceAttribute]
115+
public class MyClass<T> : IMyClass<T>
116+
{
117+
}
118+
""";
119+
120+
await TestHelper.VerifyAsync(source, SnapshotsDirectory);
121+
}
122+
123+
[Fact]
124+
public async Task GenericClassWithTypeConstraints_Class()
125+
{
126+
var source = """
127+
using ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.Attributes;
128+
namespace ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.UnitTestClasses;
129+
130+
[GenerateInterfaceAttribute]
131+
public class MyClass<T> : IMyClass<T> where T : class
132+
{
133+
}
134+
""";
135+
136+
await TestHelper.VerifyAsync(source, SnapshotsDirectory);
137+
}
138+
139+
[Fact]
140+
public async Task GenericClassWithTypeConstraints_BaseClass()
141+
{
142+
var source = """
143+
using ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.Attributes;
144+
namespace ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.UnitTestClasses;
145+
146+
public class MyBase { }
147+
148+
[GenerateInterfaceAttribute]
149+
public class MyClass<T> : IMyClass<T> where T : MyBase
150+
{
151+
}
152+
""";
153+
154+
await TestHelper.VerifyAsync(source, SnapshotsDirectory);
155+
}
156+
157+
[Fact]
158+
public async Task GenericClassWithTypeConstraints_EmptyConstructor()
159+
{
160+
var source = """
161+
using ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.Attributes;
162+
namespace ProgrammerAl.SourceGenerators.PublicInterfaceGenerator.UnitTestClasses;
163+
164+
public class MyBase { }
165+
166+
[GenerateInterfaceAttribute]
167+
public class MyClass<T> : IMyClass<T> where T : new()
168+
{
169+
}
170+
""";
171+
172+
await TestHelper.VerifyAsync(source, SnapshotsDirectory);
173+
}
104174
}
105175
#pragma warning restore IDE0058 // Expression value is never used

0 commit comments

Comments
 (0)