8000 feat(FluentMethod): support parameter modifiers (#6) · m31coding/M31.FluentAPI@ee75c9b · GitHub
[go: up one dir, main page]

Skip to content

Commit ee75c9b

Browse files
authored
feat(FluentMethod): support parameter modifiers (#6)
* feat(FluentMethod): support parameter modifiers * feat(FluentMethod): support parameter modifiers for private methods * improve(ReflectionGeneration): call Invoke method with object?[] instead of object[]
1 parent 3e69734 commit ee75c9b

File tree

25 files changed

+701
-41
lines changed

25 files changed

+701
-41
lines changed

src/M31.FluentApi.Generator/CodeBuilding/Parameter.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,32 @@ namespace M31.FluentApi.Generator.CodeBuilding;
22

33
internal class Parameter : ICode
44
{
5-
internal Parameter(string type, string name, string? defaultValue = null)
5+
internal Parameter(
6+
string type,
7+
string name,
8+
string? defaultValue = null,
9+
ParameterAnnotations? parameterAnnotations = null)
610
{
711
Type = type;
812
Name = name;
913
DefaultValue = defaultValue;
14+
ParameterAnnotations = parameterAnnotations;
1015
}
1116

1217
internal string Type { get; }
1318
internal string Name { get; }
1419
internal string? DefaultValue { get; }
20+
internal ParameterAnnotations? ParameterAnnotations { get; }
21+
22+
internal bool HasAnnotation(ParameterKinds parameterKinds)
23+
{
24+
return ParameterAnnotations != null && ParameterAnnotations.Contains(parameterKinds);
25+
}
1526

1627
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
1728
{
18-
return codeBuilder.Append(Type).Space().Append(Name).Append($" = {DefaultValue}", DefaultValue != null);
29+
return codeBuilder
30+
.Append(ParameterAnnotations)
31+
.Append(Type).Space().Append(Name).Append($" = {DefaultValue}", DefaultValue != null);
1932
}
2033
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace M31.FluentApi.Generator.CodeBuilding;
2+
3+
internal class ParameterAnnotations : ICode
4+
{
5+
internal ParameterAnnotations(ParameterKinds parameterKinds)
6+
{
7+
ParameterKinds = parameterKinds;
8+
}
9+
10+
internal ParameterKinds ParameterKinds { get; }
11+
12+
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
13+
{
14+
return codeBuilder
15+
.Append("params ", Contains(ParameterKinds.Params))
16+
.Append("ref ", Contains(ParameterKinds.Ref))
17+
.Append("in ", Contains(ParameterKinds.In))
18+
.Append("out ", Contains(ParameterKinds.Out));
19+
}
20+
21+
internal bool Contains(ParameterKinds parameterKinds)
22+
{
23+
return ParameterKinds.HasFlag(parameterKinds);
24+
}
25+
26+
internal string ToCallsiteAnnotations()
27+
{
28+
if (ParameterKinds == ParameterKinds.None)
29+
{
30+
return string.Empty;
31+
}
32+
33+
return new CodeBuilder()
34+
.Append("ref ", Contains(ParameterKinds.Ref))
35+
.Append("in ", Contains(ParameterKinds.In))
36+
.Append("out ", Contains(ParameterKinds.Out)).ToString();
37+
}
38+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace M31.FluentApi.Generator.CodeBuilding;
2+
3+
[Flags]
4+
internal enum ParameterKinds
5+
{
6+
None = 0,
7+
Params = 1 << 0,
8+
Ref = 1 << 1,
9+
In = 1 << 2,
10+
Out = 1 << 3,
11+
}

src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,7 @@ internal BuilderMethod CreateBuilderMethod(string methodSymbolName, string metho
5858

5959
List<string> BuildBodyCode(string instancePrefix)
6060
{
61-
return new List<string>()
62-
{
63-
methodToCallMethodCode[methodIdentity]
64-
.BuildCode(instancePrefix, parameters.Select(p => p.Name).ToArray()),
65-
};
61+
return methodToCallMethodCode[methodIdentity].BuildCode(instancePrefix, parameters);
6662
}
6763

6864
return new BuilderMethod(methodName, parameters, BuildBodyCode);

src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/InnerBodyGeneration/LineForMethodGenerator.cs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,88 @@ protected override string SymbolType(MethodSymbolInfo symbolInfo)
2020

2121
protected override void GenerateLineWithoutReflection(MethodSymbolInfo symbolInfo)
2222
{
23-
// createStudent.student.InSemester(semester);
24-
CallMethodCode callMethodCode =
25-
new CallMethodCode((instancePrefix, values) =>
26-
$"{instancePrefix}{CodeBoard.Info.ClassInstanceName}.{symbolInfo.Name}({string.Join(", ", values)});");
23+
CallMethodCode callMethodCode = new CallMethodCode(BuildCallMethodCode);
2724
CodeBoard.MethodToCallMethodCode[CreateMethodIdentity(symbolInfo)] = callMethodCode;
25+
26+
List<string> BuildCallMethodCode(string instancePrefix, IReadOnlyCollection<Parameter> outerMethodParameters)
27+
{
28+
return new List<string>()
29+
{
30+
// createStudent.student.InSemester(semester);
31+
$"{instancePrefix}{CodeBoard.Info.ClassInstanceName}.{symbolInfo.Name}" +
32+
$"({string.Join(", ", outerMethodParameters.Select(CreateArgument))});",
33+
};
34+
}
35+
36+
static string CreateArgument(Parameter outerMethodParameter)
37+
{
38+
// ref/in/out semester
39+
return $"{outerMethodParameter.ParameterAnnotations?.ToCallsiteAnnotations()}{outerMethodParameter.Name}";
40+
}
2841
}
2942

3043
protected override void GenerateLineWithReflection(MethodSymbolInfo symbolInfo, string infoFieldName)
3144
{
32-
// semesterMethodInfo.Invoke(createStudent.student, new object[] { semester });
33-
CallMethodCode callMethodCode =
34-
new CallMethodCode((instancePrefix, values) =>
35-
$"{infoFieldName}.Invoke({instancePrefix}{CodeBoard.Info.ClassInstanceName}, " +
36-
$"new object[] {{ {string.Join(", ", values)} }});");
45+
CallMethodCode callMethodCode = new CallMethodCode(BuildCallMethodCode);
3746
CodeBoard.MethodToCallMethodCode[CreateMethodIdentity(symbolInfo)] = callMethodCode;
47+
48+
List<string> BuildCallMethodCode(string instancePrefix, IReadOnlyCollection<Parameter> outerMethodParameters)
49+
{
50+
return outerMethodParameters.Any(p =>
51+
p.HasAnnotation(ParameterKinds.Ref) || p.HasAnnotation(ParameterKinds.Out))
52+
? BuildReflectionCodeWithParameterModifiers(infoFieldName, instancePrefix, outerMethodParameters)
53+
: BuildDefaultReflectionCode(infoFieldName, instancePrefix, outerMethodParameters);
54+
}
55+
}
56+
57+
private List<string> BuildReflectionCodeWithParameterModifiers(
58+
string infoFieldName,
59+
string instancePrefix,
60+
IReadOnlyCollection<Parameter> outerMethodParameters)
61+
{
62+
List<string> lines = new List<string>();
63+
64+
// object?[] args = new object?[] { semester };
65+
lines.Add(
66+
$"object?[] args = new object?[] {{ {string.Join(", ", outerMethodParameters.Select(GetArgument))} }};");
67+
68+
// semesterMethodInfo.Invoke(createStudent.student, args)
69+
lines.Add($"{infoFieldName}.Invoke({instancePrefix}{CodeBoard.Info.ClassInstanceName}, args);");
70+
71+
foreach (var parameter in outerMethodParameters.Select((p, i) => new { Value = p, Index = i }))
72+
{
73+
if (parameter.Value.HasAnnotation(ParameterKinds.Ref) || parameter.Value.HasAnnotation(ParameterKinds.Out))
74+
{
75+
lines.Add(AssignValueToArgument(parameter.Index, parameter.Value));
76+
}
77+
}
78+
79+
return lines;
80+
81+
string GetArgument(Parameter parameter)
82+
{
83+
return parameter.HasAnnotation(ParameterKinds.Out) ? "null" : parameter.Name;
84+
}
85+
86+
string AssignValueToArgument(int index, Parameter parameter)
87+
{
88+
// For out and ref parameters, the invoke method writes the result values into the args array.
89+
// semester = (int) args[0];
90+
return $"{parameter.Name} = ({parameter.Type}) args[{index}]!;";
91+
}
92+
}
93+
94+
private List<string> BuildDefaultReflectionCode(
95+
string infoFieldName,
96+
string instancePrefix,
97+
IReadOnlyCollection<Parameter> outerMethodParameters)
98+
{
99+
return new List<string>()
100+
{
101+
// semesterMethodInfo.Invoke(createStudent.student, new object[] { semester });
102+
$"{infoFieldName}.Invoke({instancePrefix}{CodeBoard.Info.ClassInstanceName}, " +
103+
$"new object?[] {{ {string.Join(", ", outerMethodParameters.Select(p => p.Name))} }});",
104+
};
38105
}
39106

40107
private static MethodIdentity CreateMethodIdentity(MethodSymbolInfo methodSymbolInfo)
@@ -63,4 +130,4 @@ protected override void InitializeInfoField(string fieldName, MethodSymbolInfo s
63130

64131
CodeBoard.CodeFile.AddUsing("System");
65132
}
66-
}
133+
}

src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Collections/CollectionMethodCreator.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ internal CollectionMethodCreator(
3030

3131
internal BuilderMethod CreateWithItemsParamsMethod(MethodCreator methodCreator)
3232
{
33-
Parameter parameter = new Parameter($"params {genericTypeArgument}[]", symbolInfo.NameInCamelCase);
33+
Parameter parameter = new Parameter(
34+
$"{genericTypeArgument}[]",
35+
symbolInfo.NameInCamelCase,
36+
null,
37+
new ParameterAnnotations(ParameterKinds.Params));
38+
3439
return methodCreator.CreateMethodWithComputedValue(
3540
symbolInfo,
3641
collectionAttributeInfo.WithItems,

src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/FluentMethods.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ internal FluentMethods(MethodSymbolInfo symbolInfo, FluentMethodAttributeInfo me
1919
public BuilderMethods CreateBuilderMethods(MethodCreator methodCreator)
2020
{
2121
List<Parameter> parameters = SymbolInfo.ParameterInfos
22-
.Select(i => new Parameter(i.TypeForCodeGeneration, i.ParameterName, i.DefaultValue)).ToList();
22+
.Select(i => new Parameter(
23+
i.TypeForCodeGeneration,
24+
i.ParameterName,
25+
i.DefaultValue,
26+
new ParameterAnnotations(i.ParameterKinds)))
27+
.ToList();
2328

2429
BuilderMethod builderMethod =
2530
methodCreator.BuilderMethodFactory.CreateBuilderMethod(
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using M31.FluentApi.Generator.CodeBuilding;
2+
3+
namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;
4+
5+
internal delegate List<string> BuildCallMethodCode(
6+
string instancePrefix,
7+
IReadOnlyCollection<Parameter> outerMethodParameters);
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1+
using M31.FluentApi.Generator.CodeBuilding;
2+
13
namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;
24

35
internal class CallMethodCode
46
{
5-
private readonly Func<string, string[], string> buildCodeWithInstancePrefixAndValues;
7+
private readonly BuildCallMethodCode buildCallMethodCode;
68

7-
internal CallMethodCode(Func<string, string[], string> buildCodeWithInstancePrefixAndValues)
9+
internal CallMethodCode(BuildCallMethodCode buildCallMethodCode)
810
{
9-
this.buildCodeWithInstancePrefixAndValues = buildCodeWithInstancePrefixAndValues;
11+
this.buildCallMethodCode = buildCallMethodCode;
1012
}
1113

12-
internal string BuildCode(string instancePrefix, string[] parameters)
14+
internal List<string> BuildCode(string instancePrefix, IReadOnlyCollection<Parameter> outerMethodParameters)
1315
{
14-
return buildCodeWithInstancePrefixAndValues(instancePrefix, parameters);
16+
return buildCallMethodCode(instancePrefix, outerMethodParameters);
1517
}
1618

1719
public override string ToString()
1820
{
19-
return buildCodeWithInstancePrefixAndValues(string.Empty, Array.Empty<string>());
21+
return string.Join(Environment.NewLine, buildCallMethodCode(string.Empty, Array.Empty<Parameter>()));
2022
}
2123
}

src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/ParameterSymbolInfo.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using M31.FluentApi.Generator.CodeBuilding;
12
using M31.FluentApi.Generator.Commons;
23

34
namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;
@@ -8,25 +9,29 @@ internal ParameterSymbolInfo(
89
string parameterName,
910
string typeForCodeGeneration,
1011
bool isNullable,
11-
string? defaultValue)
12+
string? defaultValue,
13+
ParameterKinds parameterKinds)
1214
{
1315
ParameterName = parameterName;
1416
TypeForCodeGeneration = typeForCodeGeneration;
1517
IsNullable = isNullable;
1618
DefaultValue = defaultValue;
19+
ParameterKinds = parameterKinds;
1720
}
1821

1922
internal string ParameterName { get; }
2023
internal string TypeForCodeGeneration { get; }
2124
internal bool IsNullable { get; }
2225
internal string? DefaultValue { get; }
26+
internal ParameterKinds ParameterKinds { get; }
2327

2428
protected bool Equals(ParameterSymbolInfo other)
2529
{
2630
return ParameterName == other.ParameterName &&
2731
TypeForCodeGeneration == other.TypeForCodeGeneration &&
2832
IsNullable == other.IsNullable &&
29-
DefaultValue == other.DefaultValue;
33+
DefaultValue == other.DefaultValue &&
34+
ParameterKinds == other.ParameterKinds;
3035
}
3136

3237
public override bool Equals(object? obj)
@@ -39,6 +44,6 @@ public override bool Equals(object? obj)
3944

4045
public override int GetHashCode()
4146
{
42-
return new HashCode().Add(ParameterName, TypeForCodeGeneration, IsNullable, DefaultValue);
47+
return new HashCode().Add(ParameterName, TypeForCodeGeneration, IsNullable, DefaultValue, ParameterKinds);
4348
}
4449
}

src/M31.FluentApi.Generator/Commons/HashCode.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ internal HashCode Add<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4
5555
return this;
5656
}
5757

58+
internal HashCode Add<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
59+
{
60+
unchecked
61+
{
62+
hash = hash * 23 + value1?.GetHashCode() ?? 0;
63+
hash = hash * 23 + value2?.GetHashCode() ?? 0;
64+
hash = hash * 23 + value3?.GetHashCode() ?? 0;
65+
hash = hash * 23 + value4?.GetHashCode() ?? 0;
66+
hash = hash * 23 + value5?.GetHashCode() ?? 0;
67+
}
68+
69+
return this;
70+
}
71+
5872
internal HashCode AddSequence<T>(IEnumerable<T> items)
5973
{
6074
unchecked

src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using M31.FluentApi.Generator.CodeBuilding;
12
using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;
23
using M31.FluentApi.Generator.Commons;
34
using M31.FluentApi.Generator.SourceGenerators.Collections;
@@ -88,7 +89,8 @@ private static ParameterSymbolInfo CreateParameterSymbolInfo(IParameterSymbol pa
8889
parameterSymbol.Name,
8990
CodeTypeExtractor.GetTypeForCodeGeneration(parameterSymbol.Type),
9091
parameterSymbol.NullableAnnotation == NullableAnnotation.Annotated,
91-
GetDefaultValueAsCode(parameterSymbol));
92+
GetDefaultValueAsCode(parameterSymbol),
93+
GetParameterKinds(parameterSymbol));
9294
}
9395

9496
private static string? GetDefaultValueAsCode(IParameterSymbol parameterSymbol)
@@ -120,4 +122,33 @@ private static ParameterSymbolInfo CreateParameterSymbolInfo(IParameterSymbol pa
120122
{ } o => o.ToString(),
121123
};
122124
}
125+
126+
private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol)
127+
{
128+
ParameterKinds parameterKinds = ParameterKinds.None;
129+
130+
if (parameterSymbol.IsParams)
131+
{
132+
parameterKinds |= ParameterKinds.Params;
133+
}
134+
135+
switch (parameterSymbol.RefKind)
136+
{
137+
case RefKind.None:
138+
break;
139+
case RefKind.Ref:
140+
parameterKinds |= ParameterKinds.Ref;
141+
break;
142+
case RefKind.Out:
143+
parameterKinds |= ParameterKinds.Out;
144+
break;
145+
case RefKind.In:
146+
parameterKinds |= ParameterKinds.In;
147+
break;
148+
default:
149+
throw new ArgumentException($"RefKind {parameterSymbol.RefKind} is not handled.");
150+
}
151+
152+
return parameterKinds;
153+
}
123154
}

0 commit comments

Comments
 (0)
0