Skip to content

Commit 2c83b5c

Browse files
authored
feat: unsafe accessors (#48)
* chore: migration to .NET 8 * test: ThreePrivateMembersClass with UnsafeAccessor * fix: remove requires reflection * wip: use unsafe accessor * fix: publiclyWritable * wip: unsafe accessor * fix: ThreePrivateMembersClass test * test: failing PrivateFluentMethodClass test * feat: InnerBodyForMethodGenerator * test: FluentReturnMultiStepPrivateMethodsClass * test: PrivateConstructorClass * test: GenericClassPrivateConstructor * test: ThreeMemberRecordPrimaryConstructor * test: CanExecuteThreeMemberRecordPrimaryConstructor * fix: ThreeMemberRecordPrimaryConstructor expected * test: ThreeMemberRecordPrimaryConstructor * fix: equality tests * test: GenericClassPrivateDefaultConstructor * wip: UsageTests * test: add GenericClassPrivateConstructor failing usage test * wip: enabled tests * chore: upgrade to .NET 10 * chore(M31.FluentApi.Tests): update nuget packages * test: ParameterAnnotationsPublicConstructorClass / ParameterAnnotationsPrivateConstructorClass * chore: address warnings * test: PrivateFluentMethodNullableParameterClass * refactor: extract CreateParameters method * test: PrivateFluentMethodParameterModifiersClass * test: PrivateReadonlyFieldClass (wip) * test: PrivateReadonlyFieldClass * test: PrivateUnderscoreFieldClass * test: PrivateFieldClass * test: PredicatePrivateFieldClass * test: InheritedRecord (wip) * fix: InheritedRecord: CreateStudent.expected.txt * test: InheritedRecord * test: InheritedClassPrivateSetters * test: GetPrivateInitPropertyClass * test: CollectionNullableArrayClass * test: ContinueWithAfterCompoundClass * test: ContinueWithOfOverloadedMethodClass * test: ContinueWithSelfClass * test: CommentedMethodsClass * test: CommentedPropertiesClassAdvanced * test: FluentNullableNoNullableAnnotationPrivateSetClass * test: PublicReadonlyFieldClass * test: PartialClass * test: TryBreakFluentApiClass1 * test: TryBreakFluentApiClass2 * test: DocumentedStudentClass * test: Student * test: ThreeMemberStruct * usage test: ContinueWithInForkClass * test: move usage tests * usage tests: migrated all tests * fix: declaring class name * test: GenericOverloadedPrivateMethodClass * test: write expected * test: GenericClassWithGenericMethods * fix: test method names * test: PrivateConstructorClassWithParams * fix: remove todo * fix: exlamation mark in set methods * chore: add example and story book project * chore: suppress warnings * chore: mention UnsafeAccessors * chore: 2.0.0 and changelog
1 parent 38b7dd4 commit 2c83b5c

File tree

190 files changed

+4054
-3218
lines changed

Some content is hidden

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

190 files changed

+4054
-3218
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [2.0.0] - 2025-12-30
9+
10+
### Changed
11+
12+
- Generated code now requires .NET 10 instead of .NET 6
13+
- UnsafeAccessors are used instead of reflection for setting private properties and fields

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ Accompanying blog post: [www.m31coding.com>blog>fluent-api](https://www.m31codin
2626
- Support for returning arbitrary types
2727
- Support for inheritance, generics, and partial classes
2828

29+
## Prerequisites
30+
31+
- v1.x.x: .NET 6
32+
- v2.x.x: .NET 10
33+
2934
## Installing via NuGet
3035

3136
Install the latest version of the package `M31.FluentApi` via your IDE or use the package manager console:
@@ -37,7 +42,7 @@ PM> Install-Package M31.FluentApi
3742
A package reference will be added to your `csproj` file. Moreover, since this library provides code via source code generation, consumers of your project don't need the reference to `M31.FluentApi`. Therefore, it is recommended to use the `PrivateAssets` metadata tag:
3843

3944
```xml
40-
<PackageReference Include="M31.FluentApi" Version="1.12.0" PrivateAssets="all"/>
45+
<PackageReference Include="M31.FluentApi" Version="2.0.0" PrivateAssets="all"/>
4146
```
4247

4348
If you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file:
@@ -112,7 +117,7 @@ public class Student
112117

113118
![fluent-api-usage](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/fluent-api.gif)
114119

115-
You may have a look at the generated code for this example: [CreateStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/StudentClass/CreateStudent.g.cs). Note that if you use private members or properties with a private set accessor, as it is the case in this example, the generated code will use reflection to set the properties.
120+
You may have a look at the generated code for this example: [CreateStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/StudentClass/CreateStudent.g.cs). Note that if you use private members or properties with a private set accessor, as it is the case in this example, the generated code will use UnsafeAccessors to set the properties.
116121

117122
## Attributes
118123

src/ExampleProject/ExampleProject.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net6.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
</PropertyGroup>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ internal class Class : ICode
44
{
55
private readonly List<Field> fields;
66
private readonly List<Method> methods;
7+
private readonly List<MethodSignature> methodSignatures;
78
private readonly List<Property> properties;
89
private readonly List<string> interfaces;
910
private readonly List<ICode> definitions;
@@ -15,6 +16,7 @@ internal Class(string name)
1516
Modifiers = new Modifiers();
1617
fields = new List<Field>();
1718
methods = new List<Method>();
19+
methodSignatures = new List<MethodSignature>();
1820
properties = new List<Property>();
1921
interfaces = new List<string>();
2022
definitions = new List<ICode>();
@@ -26,6 +28,7 @@ internal Class(string name)
2628
internal Modifiers Modifiers { get; }
2729
internal IReadOnlyCollection<Field> Fields => fields;
2830
internal IReadOnlyCollection<Method> Methods => methods;
31+
internal IReadOnlyCollection<MethodSignature> MethodSignatures => methodSignatures;
2932
internal IReadOnlyCollection<Property> Properties => properties;
3033
internal IReadOnlyCollection<string> Interfaces => interfaces;
3134
internal IReadOnlyCollection<ICode> Definitions => definitions;
@@ -55,6 +58,11 @@ internal void AddMethod(Method method)
5558
methods.Add(method);
5659
}
5760

61+
internal void AddMethodSignature(MethodSignature methodSignature)
62+
{
63+
methodSignatures.Add(methodSignature);
64+
}
65+
5866
internal void AddProperty(Property property)
5967
{
6068
properties.Add(property);
@@ -113,6 +121,7 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder)
113121
.BlankLine()
114122
.AppendWithBlankLines(methods)
115123
.AppendWithBlankLines(definitions)
124+
.AppendWithBlankLines(methodSignatures)
116125
.CloseBlock();
117126
}
118127
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal Interface(string accessModifier, string name)
2222

2323
internal void AddMethodSignature(CommentedMethodSignature methodSignature)
2424
{
25-
if (!methodSignature.MethodSignature.IsSignatureForInterface)
25+
if (!methodSignature.MethodSignature.IsStandaloneSignature)
2626
{
2727
throw new ArgumentException("Expected a stand-alone method signature.");
2828
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal Method(MethodComments methodComments, MethodSignature methodSignature,
1919
internal MethodComments MethodComments { get; }
2020
internal MethodSignature MethodSignature { get; }
2121
internal MethodBody MethodBody { get; }
22+
internal bool IsConstructor => MethodSignature.IsConstructor;
2223

2324
internal void AddCommentLine(string commentLine)
2425
{

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,37 @@ private MethodSignature(
66
string? returnType,
77
string methodName,
88
string? explicitInterfacePrefix,
9-
bool isSignatureForInterface)
9+
bool isStandaloneSignature)
1010
{
1111
ReturnType = returnType;
1212
MethodName = methodName;
1313
ExplicitInterfacePrefix = explicitInterfacePrefix;
14-
IsSignatureForInterface = isSignatureForInterface;
14+
IsStandaloneSignature = isStandaloneSignature;
1515
Generics = new Generics();
1616
Parameters = new Parameters();
1717
Modifiers = new Modifiers();
18+
Attributes = new List<string>();
1819
}
1920

20-
private MethodSignature(MethodSignature methodSignature, bool isSignatureForInterface)
21+
private MethodSignature(MethodSignature methodSignature, bool isStandaloneSignature)
2122
{
2223
ReturnType = methodSignature.ReturnType;
2324
MethodName = methodSignature.MethodName;
2425
ExplicitInterfacePrefix = methodSignature.ExplicitInterfacePrefix;
25-
IsSignatureForInterface = isSignatureForInterface;
26+
IsStandaloneSignature = isStandaloneSignature;
2627
Generics = new Generics(methodSignature.Generics);
2728
Parameters = new Parameters(methodSignature.Parameters);
2829
Modifiers = new Modifiers(methodSignature.Modifiers);
30+
Attributes = new List<string>(methodSignature.Attributes);
2931
}
3032

3133
internal static MethodSignature Create(
3234
string returnType,
3335
string methodName,
3436
string? prefix,
35-
bool isSignatureForInterface)
37+
bool isStandaloneSignature)
3638
{
37-
return new MethodSignature(returnType, methodName, prefix, isSignatureForInterface);
39+
return new MethodSignature(returnType, methodName, prefix, isStandaloneSignature);
3840
}
3941

4042
internal static MethodSignature CreateConstructorSignature(string className)
@@ -45,12 +47,14 @@ internal static MethodSignature CreateConstructorSignature(string className)
4547
internal string? ReturnType { get; }
4648
internal string MethodName { get; }
4749
internal string? ExplicitInterfacePrefix { get; }
48-
internal bool IsSignatureForInterface { get; }
50+
internal bool IsStandaloneSignature { get; }
4951
internal Generics Generics { get; }
5052
internal Parameters Parameters { get; }
5153
internal Modifiers Modifiers { get; }
52-
internal bool IsSignatureForMethodBody => !IsSignatureForInterface;
54+
internal List<string> Attributes { get; }
55+
internal bool IsSignatureForMethodBody => !IsStandaloneSignature;
5356
internal bool IsExplicitInterfaceImplementation => ExplicitInterfacePrefix != null;
57+
internal bool IsConstructor => ReturnType == null;
5458

5559
internal void AddGenericParameter(string parameter, IEnumerable<string> constraints)
5660
{
@@ -72,6 +76,11 @@ internal void AddModifiers(params string[] modifiers)
7276
Modifiers.Add(modifiers);
7377
}
7478

79+
internal void AddAttribute(string attribute)
80+
{
81+
Attributes.Add(attribute);
82+
}
83+
7584
internal MethodSignature ToSignatureForInterface()
7685
{
7786
return new MethodSignature(this, true);
@@ -85,8 +94,11 @@ internal MethodSignature ToSignatureForMethodBody()
8594
public CodeBuilder AppendCode(CodeBuilder codeBuilder)
8695
{
8796
codeBuilder
97+
.AppendLines(Attributes)
8898
.StartLine()
89-
.Append(Modifiers, IsSignatureForMethodBody && !IsExplicitInterfaceImplementation)
99+
.Append(
100+
Modifiers,
101+
Modifiers.Contains("extern") || (IsSignatureForMethodBody && !IsExplicitInterfaceImplementation))
90102
.Append($"{ReturnType} ", ReturnType != null)
91103
.Append($"{ExplicitInterfacePrefix}.", IsSignatureForMethodBody && IsExplicitInterfaceImplementation)
92104
.Append(MethodName)
@@ -96,15 +108,15 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder)
96108

97109
if (Generics.Constraints.Count == 0 || (IsSignatureForMethodBody && IsExplicitInterfaceImplementation))
98110
{
99-
return codeBuilder.Append(IsSignatureForInterface ? ";" : null).EndLine();
111+
return codeBuilder.Append(IsStandaloneSignature ? ";" : null).EndLine();
100112
}
101113
else
102114
{
103115
return codeBuilder
104116
.EndLine()
105117
.Indent()
106118
.Append(Generics.Constraints)
107-
.Append(IsSignatureForInterface ? ";" : null)
119+
.Append(IsStandaloneSignature ? ";" : null)
108120
.EndLine()
109121
.Unindent();
110122
}

src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,7 @@ private MethodSignature CreateMethodSignature(
5050

5151
MethodSignature signature = MethodSignature.Create(returnType, MethodName, explicitInterfacePrefix, false);
5252
signature.AddModifiers(modifiers);
53-
54-
if (GenericInfo != null)
55-
{
56-
foreach (GenericTypeParameter genericTypeParameter in GenericInfo.Parameters)
57-
{
58-
signature.AddGenericParameter(
59-
genericTypeParameter.ParameterName,
60-
genericTypeParameter.Constraints.GetConstraintsForCodeGeneration());
61-
}
62-
}
53+
CodeBuildingHelpers.AddGenericParameters(signature, GenericInfo);
6354

6455
foreach (Parameter parameter in Parameters)
6556
{

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,7 @@ internal BuilderMethod CreateBuilderMethod(
8585
string methodName,
8686
bool respectReturnType)
8787
{
88-
List<Parameter> parameters = methodSymbolInfo.ParameterInfos
89-
.Select(i => new Parameter(
90-
i.TypeForCodeGeneration,
91-
i.ParameterName,
92-
i.DefaultValue,
93-
i.GenericTypeParameterPosition,
94-
new ParameterAnnotations(i.ParameterKinds)))
95-
.ToList();
96-
88+
List<Parameter> parameters = CodeBuildingHelpers.CreateParameters(methodSymbolInfo.ParameterInfos);
9789
string? returnTypeToRespect = respectReturnType ? methodSymbolInfo.ReturnType : null;
9890

9991
List<string> BuildBodyCode(
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using M31.FluentApi.Generator.CodeBuilding;
2+
using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements;
3+
using M31.FluentApi.Generator.SourceGenerators.Generics;
4+
5+
namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons;
6+
7+
internal static class CodeBuildingHelpers
8+
{
9+
internal static void AddGenericParameters(MethodSignature methodSignature, GenericInfo? genericInfo)
10+
{
11+
if (genericInfo == null)
12+
{
13+
return;
14+
}
15+
16+
foreach (GenericTypeParameter genericTypeParameter in genericInfo.Parameters)
17+
{
18+
methodSignature.AddGenericParameter(
19+
genericTypeParameter.ParameterName,
20+
genericTypeParameter.Constraints.GetConstraintsForCodeGeneration());
21+
}
22+
}
23+
24+
internal static List<Parameter> CreateParameters(IReadOnlyCollection<ParameterSymbolInfo> parameterInfos)
25+
{
26+
return parameterInfos
27+
.Select(i => new Parameter(
28+
i.TypeForCodeGeneration,
29+
i.ParameterName,
30+
i.DefaultValue,
31+
i.GenericTypeParameterPosition,
32+
new ParameterAnnotations(i.ParameterKinds)))
33+
.ToList();
34+
}
35+
}

0 commit comments

Comments
 (0)