Skip to content

Commit 7555d5b

Browse files
NikolayPianikovNikolayPianikov
authored andcommitted
Explicitly select a constructor by marking it with the [Immutype.Target] attribute
1 parent 2c660a1 commit 7555d5b

File tree

8 files changed

+195
-10
lines changed

8 files changed

+195
-10
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// ReSharper disable ClassNeverInstantiated.Global
2+
// ReSharper disable MemberCanBePrivate.Global
3+
// ReSharper disable UnusedMember.Global
4+
// ReSharper disable RedundantNameQualifier
5+
// ReSharper disable CheckNamespace
6+
namespace Immutype.UsageScenarios.Tests.ExplicitConstructorChoice
7+
{
8+
using System.Collections.Immutable;
9+
using Shouldly;
10+
using Xunit;
11+
12+
// $visible=true
13+
// $tag=1 Basics
14+
// $priority=06
15+
// $description=Explicit constructor choice
16+
// {
17+
[Immutype.Target]
18+
internal readonly struct Person
19+
{
20+
public readonly string Name;
21+
public readonly int Age;
22+
public readonly IImmutableList<Person> Friends;
23+
24+
// You can explicitly select a constructor by marking it with the [Immutype.Target] attribute
25+
[Immutype.Target]
26+
public Person(
27+
string name,
28+
int age = 0,
29+
IImmutableList<Person>? friends = default)
30+
{
31+
Name = name;
32+
Age = age;
33+
Friends = friends ?? ImmutableList<Person>.Empty;
34+
}
35+
36+
public Person(
37+
string name,
38+
int age,
39+
IImmutableList<Person>? friends,
40+
int someArg = 99)
41+
{
42+
Name = name;
43+
Age = age;
44+
Friends = friends ?? ImmutableList<Person>.Empty;
45+
}
46+
};
47+
48+
public class ExplicitConstructorChoice
49+
{
50+
// }
51+
[Fact]
52+
// {
53+
public void Run()
54+
{
55+
var john = new Person("John",15)
56+
.WithFriends(
57+
new Person("David").WithAge(16),
58+
new Person("James").WithAge(17))
59+
.AddFriends(
60+
new Person("David").WithAge(22));
61+
62+
john.Friends.Count.ShouldBe(3);
63+
}
64+
}
65+
// }
66+
}

Immutype.UsageScenarios.Tests/README_TEMPLATE.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [Nullable collection](#nullable-collection)
1111
- [Set](#set)
1212
- [Record with constructor](#record-with-constructor)
13+
- [Explicit constructor choice](#explicit-constructor-choice)
1314

1415
### Sample scenario
1516

@@ -281,3 +282,57 @@ public class RecordWithConstructor
281282

282283

283284

285+
### Explicit constructor choice
286+
287+
288+
289+
``` CSharp
290+
[Immutype.Target]
291+
internal readonly struct Person
292+
{
293+
public readonly string Name;
294+
public readonly int Age;
295+
public readonly IImmutableList<Person> Friends;
296+
297+
// You can explicitly select a constructor by marking it with the [Immutype.Target] attribute
298+
[Immutype.Target]
299+
public Person(
300+
string name,
301+
int age = 0,
302+
IImmutableList<Person>? friends = default)
303+
{
304+
Name = name;
305+
Age = age;
306+
Friends = friends ?? ImmutableList<Person>.Empty;
307+
}
308+
309+
public Person(
310+
string name,
311+
int age,
312+
IImmutableList<Person>? friends,
313+
int someArg = 99)
314+
{
315+
Name = name;
316+
Age = age;
317+
Friends = friends ?? ImmutableList<Person>.Empty;
318+
}
319+
};
320+
321+
public class ExplicitConstructorChoice
322+
{
323+
public void Run()
324+
{
325+
var john = new Person("John",15)
326+
.WithFriends(
327+
new Person("David").WithAge(16),
328+
new Person("James").WithAge(17))
329+
.AddFriends(
330+
new Person("David").WithAge(22));
331+
332+
john.Friends.Count.ShouldBe(3);
333+
}
334+
}
335+
```
336+
337+
338+

Immutype/Components/Contracts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ namespace Immutype
44
{
55
using System;
66

7-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
7+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor, Inherited = false)]
88
public class TargetAttribute: Attribute { }
99
}

Immutype/Core/ISyntaxNodeFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace Immutype.Core
66

77
internal interface ISyntaxNodeFactory
88
{
9+
bool HasTargetAttribute(MemberDeclarationSyntax memberDeclarationSyntax);
10+
911
TypeSyntax? GetUnqualified(TypeSyntax? typeSyntax);
1012

1113
bool IsAccessible(IEnumerable<SyntaxToken> modifiers);

Immutype/Core/SourceBuilder.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ internal class SourceBuilder: ISourceBuilder
1616
{
1717
private readonly ITypeSyntaxFilter _typeSyntaxFilter;
1818
private readonly IUnitFactory _unitFactory;
19+
private readonly ISyntaxNodeFactory _syntaxNodeFactory;
1920

2021
public SourceBuilder(
2122
ITypeSyntaxFilter typeSyntaxFilter,
22-
IUnitFactory unitFactory)
23+
IUnitFactory unitFactory,
24+
ISyntaxNodeFactory syntaxNodeFactory)
2325
{
2426
_typeSyntaxFilter = typeSyntaxFilter;
2527
_unitFactory = unitFactory;
28+
_syntaxNodeFactory = syntaxNodeFactory;
2629
}
2730

2831
public IEnumerable<Source> Build(IEnumerable<SyntaxTree> trees, CancellationToken cancellationToken) =>
@@ -46,7 +49,8 @@ public IEnumerable<Source> Build(TypeDeclarationSyntax typeDeclarationSyntax, Ca
4649
from ctor in typeDeclarationSyntax.Members.OfType<ConstructorDeclarationSyntax>()
4750
where !cancellationToken.IsCancellationRequested
4851
where ctor.ParameterList.Parameters.Count > 0 && ctor.Modifiers.Any(i => i.IsKind(SyntaxKind.PublicKeyword) || i.IsKind(SyntaxKind.InternalKeyword)) || !ctor.Modifiers.Any()
49-
orderby ctor.ParameterList.Parameters.Count descending
52+
let weight = ctor.ParameterList.Parameters.Count + (_syntaxNodeFactory.HasTargetAttribute(ctor) ? 0xffff : 0)
53+
orderby weight descending
5054
select ctor)
5155
.FirstOrDefault()
5256
?.ParameterList

Immutype/Core/SyntaxNodeFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ internal class SyntaxNodeFactory : ISyntaxNodeFactory
2323

2424
private static readonly AttributeSyntax PureAttr = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName(typeof(PureAttribute).FullName));
2525

26+
public bool HasTargetAttribute(MemberDeclarationSyntax memberDeclarationSyntax) =>
27+
memberDeclarationSyntax.AttributeLists
28+
.SelectMany(i => i.Attributes)
29+
.Select(i => GetUnqualified(i.Name)?.ToString())
30+
.Any(i => i is "Target" or "TargetAttribute");
31+
2632
public TypeSyntax? GetUnqualified(TypeSyntax? typeSyntax) =>
2733
typeSyntax is QualifiedNameSyntax qualifiedType ? qualifiedType.Right : typeSyntax;
2834

Immutype/Core/TypeSyntaxFilter.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ internal class TypeSyntaxFilter : ITypeSyntaxFilter
1010
{
1111
private readonly ISyntaxNodeFactory _syntaxNodeFactory;
1212

13-
public TypeSyntaxFilter(ISyntaxNodeFactory syntaxNodeFactory)
14-
{
13+
public TypeSyntaxFilter(ISyntaxNodeFactory syntaxNodeFactory) =>
1514
_syntaxNodeFactory = syntaxNodeFactory;
16-
}
1715

1816
public bool IsAccepted(TypeDeclarationSyntax typeDeclarationSyntax)
1917
{
@@ -27,10 +25,7 @@ public bool IsAccepted(TypeDeclarationSyntax typeDeclarationSyntax)
2725
return false;
2826
}
2927

30-
if (typeDeclarationSyntax.AttributeLists
31-
.SelectMany(i => i.Attributes)
32-
.Select(i => _syntaxNodeFactory.GetUnqualified(i.Name)?.ToString())
33-
.All(i => i != "Target" && i != "TargetAttribute"))
28+
if (!_syntaxNodeFactory.HasTargetAttribute(typeDeclarationSyntax))
3429
{
3530
return false;
3631
}
@@ -44,5 +39,7 @@ public bool IsAccepted(TypeDeclarationSyntax typeDeclarationSyntax)
4439
.OfType<ConstructorDeclarationSyntax>()
4540
.Any(ctor => ctor.ParameterList.Parameters.Count > 0 && ctor.Modifiers.Any(i => i.IsKind(SyntaxKind.PublicKeyword) || i.IsKind(SyntaxKind.InternalKeyword)) || !ctor.Modifiers.Any());
4641
}
42+
43+
4744
}
4845
}

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ _Immutype_ supports [IIncrementalGenerator](https://docs.microsoft.com/en-us/dot
9696
- [Nullable collection](#nullable-collection)
9797
- [Set](#set)
9898
- [Record with constructor](#record-with-constructor)
99+
- [Explicit constructor choice](#explicit-constructor-choice)
99100

100101
### Sample scenario
101102

@@ -367,3 +368,57 @@ public class RecordWithConstructor
367368

368369

369370

371+
### Explicit constructor choice
372+
373+
374+
375+
``` CSharp
376+
[Immutype.Target]
377+
internal readonly struct Person
378+
{
379+
public readonly string Name;
380+
public readonly int Age;
381+
public readonly IImmutableList<Person> Friends;
382+
383+
// You can explicitly select a constructor by marking it with the [Immutype.Target] attribute
384+
[Immutype.Target]
385+
public Person(
386+
string name,
387+
int age = 0,
388+
IImmutableList<Person>? friends = default)
389+
{
390+
Name = name;
391+
Age = age;
392+
Friends = friends ?? ImmutableList<Person>.Empty;
393+
}
394+
395+
public Person(
396+
string name,
397+
int age,
398+
IImmutableList<Person>? friends,
399+
int someArg = 99)
400+
{
401+
Name = name;
402+
Age = age;
403+
Friends = friends ?? ImmutableList<Person>.Empty;
404+
}
405+
};
406+
407+
public class ExplicitConstructorChoice
408+
{
409+
public void Run()
410+
{
411+
var john = new Person("John",15)
412+
.WithFriends(
413+
new Person("David").WithAge(16),
414+
new Person("James").WithAge(17))
415+
.AddFriends(
416+
new Person("David").WithAge(22));
417+
418+
john.Friends.Count.ShouldBe(3);
419+
}
420+
}
421+
```
422+
423+
424+

0 commit comments

Comments
 (0)