Skip to content

Commit aacd669

Browse files
NikolayPianikovNikolayPianikov
authored andcommitted
Support generic types
1 parent 9c17d3d commit aacd669

File tree

7 files changed

+169
-6
lines changed

7 files changed

+169
-6
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Shouldly;
2+
using Xunit;
3+
// ReSharper disable StringLiteralTypo
4+
5+
namespace Immutype.Tests.Integration
6+
{
7+
using System.Security.Cryptography.X509Certificates;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
11+
public class GenericTypeTests
12+
{
13+
[Fact]
14+
public void ShouldSupportGenerics()
15+
{
16+
// Given
17+
const string statements = "System.Console.WriteLine(string.Join(',', new Rec<int, string>(new List<int>{33}).WithVals(55).AddVals(99, 44).vals));";
18+
19+
// When
20+
var output = @"
21+
namespace Sample
22+
{
23+
using System;
24+
using System.Collections.Generic;
25+
using Immutype;
26+
27+
[Target]
28+
public record Rec<T, T2>(IList<T> vals);
29+
}".Run(out var generatedCode, new RunOptions { Statements = statements });
30+
31+
// Then
32+
output.ShouldBe(new [] { "55,99,44" }, generatedCode);
33+
}
34+
35+
[Fact]
36+
public void ShouldSupportGenericConstraints()
37+
{
38+
// Given
39+
const string statements = "System.Console.WriteLine(string.Join(',', new Rec<int, string>(new List<int>{33}).WithVals(55).AddVals(99, 44).vals));";
40+
41+
// When
42+
var output = @"
43+
namespace Sample
44+
{
45+
using System;
46+
using System.Collections.Generic;
47+
using Immutype;
48+
49+
[Target]
50+
public record Rec<T, T2>(IList<T> vals)
51+
where T: struct;
52+
}".Run(out var generatedCode, new RunOptions { Statements = statements });
53+
54+
// Then
55+
output.ShouldBe(new [] { "55,99,44" }, generatedCode);
56+
}
57+
}
58+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// ReSharper disable ClassNeverInstantiated.Global
2+
// ReSharper disable MemberCanBePrivate.Global
3+
// ReSharper disable UnusedMember.Global
4+
// ReSharper disable RedundantNameQualifier
5+
// ReSharper disable CheckNamespace
6+
// ReSharper disable NotAccessedPositionalProperty.Global
7+
namespace Immutype.UsageScenarios.Tests.GenericTypes
8+
{
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
using Shouldly;
12+
using Xunit;
13+
14+
// $visible=true
15+
// $tag=1 Basics
16+
// $priority=03
17+
// $description=Generic types
18+
// $header=It is possible to use generic types including any generic constraints.
19+
// {
20+
[Immutype.Target]
21+
internal record Person<TAge>(string Name, TAge Age = default, IEnumerable<Person<TAge>>? Friends = default)
22+
where TAge : struct;
23+
24+
public class GenericTypes
25+
{
26+
// }
27+
[Fact]
28+
// {
29+
public void Run()
30+
{
31+
var john = new Person<int>("John")
32+
.WithAge(15)
33+
.WithFriends(new Person<int>("David").WithAge(16))
34+
.AddFriends(
35+
new Person<int>("James"),
36+
new Person<int>("Daniel").WithAge(17));
37+
38+
john.Friends?.Count().ShouldBe(3);
39+
}
40+
}
41+
// }
42+
}

Immutype.UsageScenarios.Tests/README_TEMPLATE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [Applying defaults](#applying-defaults)
88
- [Immutable collection](#immutable-collection)
99
- [Removing](#removing)
10+
- [Generic types](#generic-types)
1011
- [Nullable collection](#nullable-collection)
1112
- [Set](#set)
1213
- [Record with constructor](#record-with-constructor)
@@ -173,6 +174,33 @@ public class Removing
173174

174175

175176

177+
### Generic types
178+
179+
It is possible to use generic types including any generic constraints.
180+
181+
``` CSharp
182+
[Immutype.Target]
183+
internal record Person<TAge>(string Name, TAge Age = default, IEnumerable<Person<TAge>>? Friends = default)
184+
where TAge : struct;
185+
186+
public class GenericTypes
187+
{
188+
public void Run()
189+
{
190+
var john = new Person<int>("John")
191+
.WithAge(15)
192+
.WithFriends(new Person<int>("David").WithAge(16))
193+
.AddFriends(
194+
new Person<int>("James"),
195+
new Person<int>("Daniel").WithAge(17));
196+
197+
john.Friends?.Count().ShouldBe(3);
198+
}
199+
}
200+
```
201+
202+
203+
176204
### Nullable collection
177205

178206

Immutype/Core/ExtensionsFactory.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public ExtensionsFactory(IMethodsFactory methodsFactory) =>
2121
public IEnumerable<Source> Create(ParseOptions parseOptions, TypeDeclarationSyntax typeDeclarationSyntax, IReadOnlyList<ParameterSyntax> parameters, CancellationToken cancellationToken)
2222
{
2323
var ns = typeDeclarationSyntax.Ancestors().OfType<NamespaceDeclarationSyntax>().Reverse().ToArray();
24-
var typeName = string.Join(".", ns.Select(i => i.Name.ToString()).Concat(new []{typeDeclarationSyntax.Identifier.Text}));
24+
var typeName =
25+
string.Join(".", ns.Select(i => i.Name.ToString())
26+
.Concat(new []{typeDeclarationSyntax.Identifier.Text + typeDeclarationSyntax.TypeParameterList}));
2527
var typeSyntax = SyntaxFactory.ParseName(typeName);
2628
var className = $"{typeDeclarationSyntax.Identifier.Text}Extensions";
2729
var extensionsClass = SyntaxFactory.ClassDeclaration(className)

Immutype/Core/MethodAddRemoveFactory.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@ public IEnumerable<MethodDeclarationSyntax> Create(TypeDeclarationSyntax targetD
3939

4040
var curParameters = parameters as ParameterSyntax[] ?? parameters.ToArray();
4141
var name = _nameService.ConvertToName(currentParameter.Identifier.Text);
42-
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"Add{name}")
42+
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"Add{name}" + targetDeclaration.TypeParameterList)
4343
.AddParameterListParameters(thisParameter, arrayParameter)
44+
.WithConstraintClauses(targetDeclaration.ConstraintClauses)
4445
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(thisParameter).ToArray())
4546
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(arrayParameter).ToArray())
4647
.AddBodyStatements(_syntaxNodeFactory.CreateReturnStatement(targetType, CreateArguments(nameof(Enumerable.Concat), targetDeclaration, thisParameter, curParameters, currentParameter, arrayParameter)));
4748

48-
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"Remove{name}")
49+
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"Remove{name}" + targetDeclaration.TypeParameterList)
4950
.AddParameterListParameters(thisParameter, arrayParameter)
51+
.WithConstraintClauses(targetDeclaration.ConstraintClauses)
5052
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(thisParameter).ToArray())
5153
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(arrayParameter).ToArray())
5254
.AddBodyStatements(_syntaxNodeFactory.CreateReturnStatement(targetType, CreateArguments(nameof(Enumerable.Except), targetDeclaration, thisParameter, curParameters, currentParameter, arrayParameter)));

Immutype/Core/MethodWithFactory.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,19 @@ public IEnumerable<MethodDeclarationSyntax> Create(TypeDeclarationSyntax targetD
4343
}
4444

4545
var name = _nameService.ConvertToName(currentParameter.Identifier.Text);
46-
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"With{name}")
46+
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"With{name}" + targetDeclaration.TypeParameterList)
4747
.AddParameterListParameters(thisParameter, newArgumentParameter)
48+
.WithConstraintClauses(targetDeclaration.ConstraintClauses)
4849
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(thisParameter).ToArray())
4950
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(newArgumentParameter).ToArray())
5051
.AddBodyStatements(_syntaxNodeFactory.CreateReturnStatement(targetType, arguments));
5152

5253
if (argumentParameter != newArgumentParameter && argumentParameter.Type is not ArrayTypeSyntax)
5354
{
5455
var args = curParameters.Select(parameter => parameter == currentParameter ? SyntaxFactory.Argument(SyntaxFactory.IdentifierName(currentParameter.Identifier)) : _syntaxNodeFactory.CreateTransientArgument(targetDeclaration, thisParameter, parameter));
55-
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"With{name}")
56+
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"With{name}" + targetDeclaration.TypeParameterList)
5657
.AddParameterListParameters(thisParameter, argumentParameter)
58+
.WithConstraintClauses(targetDeclaration.ConstraintClauses)
5759
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(thisParameter).ToArray())
5860
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(argumentParameter).ToArray())
5961
.AddBodyStatements(_syntaxNodeFactory.CreateReturnStatement(targetType, args));
@@ -62,8 +64,9 @@ public IEnumerable<MethodDeclarationSyntax> Create(TypeDeclarationSyntax targetD
6264
if (currentParameter.Default != default && argumentParameter == newArgumentParameter)
6365
{
6466
var args = curParameters.Select(parameter => parameter == currentParameter ? SyntaxFactory.Argument(currentParameter.Default.Value) : _syntaxNodeFactory.CreateTransientArgument(targetDeclaration, thisParameter, parameter));
65-
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"WithDefault{name}")
67+
yield return _syntaxNodeFactory.CreateExtensionMethod(targetType, $"WithDefault{name}" + targetDeclaration.TypeParameterList)
6668
.AddParameterListParameters(thisParameter)
69+
.WithConstraintClauses(targetDeclaration.ConstraintClauses)
6770
.AddBodyStatements(_syntaxNodeFactory.CreateGuards(thisParameter).ToArray())
6871
.AddBodyStatements(_syntaxNodeFactory.CreateReturnStatement(targetType, args));
6972
}

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ _Immutype_ supports [IIncrementalGenerator](https://docs.microsoft.com/en-us/dot
9393
- [Applying defaults](#applying-defaults)
9494
- [Immutable collection](#immutable-collection)
9595
- [Removing](#removing)
96+
- [Generic types](#generic-types)
9697
- [Nullable collection](#nullable-collection)
9798
- [Set](#set)
9899
- [Record with constructor](#record-with-constructor)
@@ -259,6 +260,33 @@ public class Removing
259260

260261

261262

263+
### Generic types
264+
265+
It is possible to use generic types including any generic constraints.
266+
267+
``` CSharp
268+
[Immutype.Target]
269+
internal record Person<TAge>(string Name, TAge Age = default, IEnumerable<Person<TAge>>? Friends = default)
270+
where TAge : struct;
271+
272+
public class GenericTypes
273+
{
274+
public void Run()
275+
{
276+
var john = new Person<int>("John")
277+
.WithAge(15)
278+
.WithFriends(new Person<int>("David").WithAge(16))
279+
.AddFriends(
280+
new Person<int>("James"),
281+
new Person<int>("Daniel").WithAge(17));
282+
283+
john.Friends?.Count().ShouldBe(3);
284+
}
285+
}
286+
```
287+
288+
289+
262290
### Nullable collection
263291

264292

0 commit comments

Comments
 (0)