Skip to content

Commit 49276fd

Browse files
committed
Showcase generic templates applying to all struct ids
A generic template can be applied to all struct-based ids by providing a ValueType primary constructor. To make it apply to both structs and string-based ids, use `object` as the primary constructor value.
1 parent ee56c59 commit 49276fd

4 files changed

Lines changed: 57 additions & 11 deletions

File tree

src/StructId.Analyzer/AnalysisExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ @this is INamedTypeSymbol namedActual &&
4040
if (iface.Is(baseTypeOrInterface))
4141
return true;
4242

43-
if (@this.BaseType?.Name.Equals("object", StringComparison.OrdinalIgnoreCase) == true)
43+
if (@this.BaseType?.Name.Equals("object", StringComparison.OrdinalIgnoreCase) == true &&
44+
@this.BaseType?.Equals(baseTypeOrInterface, SymbolEqualityComparer.Default) != true)
4445
return false;
4546

4647
return Is(@this.BaseType, baseTypeOrInterface);

src/StructId.Analyzer/TemplatedGenerator.cs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Text;
34
using System.Text.RegularExpressions;
45
using Microsoft.CodeAnalysis;
@@ -12,15 +13,15 @@ namespace StructId;
1213
[Generator(LanguageNames.CSharp)]
1314
public class TemplatedGenerator : IIncrementalGenerator
1415
{
15-
record KnownTypes(INamedTypeSymbol String, INamedTypeSymbol? IStructId, INamedTypeSymbol? TStructId, INamedTypeSymbol? TStructIdT);
16+
record KnownTypes(string StructIdNamespace, INamedTypeSymbol String, INamedTypeSymbol? IStructId, INamedTypeSymbol? TStructId, INamedTypeSymbol? TStructIdT);
1617
record IdTemplate(INamedTypeSymbol StructId, Template Template);
17-
record Template(INamedTypeSymbol TSelf, ITypeSymbol TId, AttributeData Attribute, bool IsGenericTId)
18+
record Template(INamedTypeSymbol TSelf, ITypeSymbol TId, AttributeData Attribute, string StructIdNamespace, bool IsGenericTId)
1819
{
1920
public Regex NameExpr { get; } = new Regex($@"\b{TSelf.Name}\b", RegexOptions.Compiled | RegexOptions.Multiline);
2021

21-
public string Text { get; } = GetTemplateCode(TSelf, TId, Attribute);
22+
public string Text { get; } = GetTemplateCode(TSelf, TId, Attribute, StructIdNamespace);
2223

23-
static string GetTemplateCode(INamedTypeSymbol self, ITypeSymbol tid, AttributeData attribute)
24+
static string GetTemplateCode(INamedTypeSymbol self, ITypeSymbol tid, AttributeData attribute, string StructIdNamespace)
2425
{
2526
if (self.DeclaringSyntaxReferences[0].GetSyntax() is not TypeDeclarationSyntax declaration)
2627
return "";
@@ -50,18 +51,48 @@ static string GetTemplateCode(INamedTypeSymbol self, ITypeSymbol tid, AttributeD
5051
root = root.ReplaceNode(update, updated);
5152
}
5253

53-
return root.SyntaxTree.GetRoot().ToFullString().Trim();
54+
// replace usings/namespace from StructId > StructIdNamespace
55+
var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>().ToList();
56+
var ns = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
57+
var nsname = ns?.Name.ToString();
58+
59+
if (nsname == "StructId")
60+
root = root.ReplaceNode(ns!, ns!.WithName(ParseName(StructIdNamespace)));
61+
else if (nsname != StructIdNamespace)
62+
usings.Add(UsingDirective(ParseName(StructIdNamespace)));
63+
64+
// deduplicate usings just in case
65+
var unique = new HashSet<string>();
66+
root = root.ReplaceNodes(usings, (old, _) =>
67+
{
68+
// replace 'StructId' > StructIdNamespace
69+
if (old.Name?.ToString() == "StructId")
70+
{
71+
unique.Add(StructIdNamespace);
72+
return old.WithName(ParseName(StructIdNamespace));
73+
}
74+
75+
if (unique.Add(old.Name?.ToString() ?? ""))
76+
return old;
77+
78+
return null!;
79+
});
80+
81+
var code = root.SyntaxTree.GetRoot().NormalizeWhitespace().ToFullString().Trim();
82+
83+
return code;
5484
}
5585
}
5686

5787
public void Initialize(IncrementalGeneratorInitializationContext context)
5888
{
59-
var targetNamespace = context.AnalyzerConfigOptionsProvider
89+
var structIdNamespace = context.AnalyzerConfigOptionsProvider
6090
.Select((x, _) => x.GlobalOptions.TryGetValue("build_property.StructIdNamespace", out var ns) ? ns : "StructId");
6191

6292
var known = context.CompilationProvider
63-
.Combine(targetNamespace)
93+
.Combine(structIdNamespace)
6494
.Select((x, _) => new KnownTypes(
95+
x.Right,
6596
// get string known type
6697
x.Left.GetTypeByMetadataName("System.String")!,
6798
x.Left.GetTypeByMetadataName($"{x.Right}.IStructId`1"),
@@ -91,14 +122,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
91122
var (structId, known) = x;
92123
var attribute = structId.GetAttributes().FirstOrDefault(a => a.AttributeClass != null && a.AttributeClass.Is(known.TStructIdT));
93124
if (attribute != null)
94-
return new Template(structId, attribute.AttributeClass!.TypeArguments[0], attribute, true);
125+
return new Template(structId, attribute.AttributeClass!.TypeArguments[0], attribute, known.StructIdNamespace, true);
95126

96127
// If we don't have the generic attribute, infer the idType from the required
97128
// primary constructor Value parameter type
98129
var idType = structId.GetMembers().OfType<IPropertySymbol>().First(p => p.Name == "Value").Type;
99130
attribute = structId.GetAttributes().First(a => a.AttributeClass != null && a.AttributeClass.Is(known.TStructId));
100131

101-
return new Template(structId, idType, attribute, false);
132+
return new Template(structId, idType, attribute, known.StructIdNamespace, false);
102133
})
103134
.Collect();
104135

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using StructId.Functional;
2+
3+
[TStructId]
4+
file partial record struct ObjectTemplate(object Value)
5+
{
6+
// applies to any struct id, whether struct or string
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using StructId.Functional;
2+
3+
[TStructId]
4+
file partial record struct StructTemplate(ValueType Value)
5+
{
6+
// applies to any ValueType-based struct id
7+
}

0 commit comments

Comments
 (0)