Skip to content

Commit a727008

Browse files
committed
Further improve docs and comment code for posterity
1 parent 24f281b commit a727008

File tree

9 files changed

+88
-44
lines changed

9 files changed

+88
-44
lines changed

readme.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,13 @@ file record struct TValue;
265265
```
266266

267267
The `TValue` is subsequently defined as a file-local type where you can
268-
specify whether it's a struct or a class and any interfaces it implements.
269-
These are used to constrain the template expansion to only apply to struct ids,
270-
such as those whose `TValue` is a struct above.
268+
specify any interfaces it implements. If no constraints need to apply to
269+
`TValue`, you can just leave the declaration empty, meaning "any value type".
270+
271+
> NOTE: The type of declaration (struct, class, record, etc.) of `TValue` is not checked,
272+
> since in many cases you'd end up having to create two versions of the same template,
273+
> one for structs and another for strings, since they are not value types and have no
274+
> common declaration type.
271275
272276
Here's another example from the built-in templates that uses this technique to
273277
apply to all struct ids whose `TValue` implements `IComparable<TValue>`:
@@ -306,14 +310,18 @@ This automatically covers not only all built-in value types, but also any custom
306310
types that implement the interface, making the code generation much more flexible
307311
and powerful.
308312

313+
> NOTE: if you need to exclude just the string type from applying to the `TValue`,
314+
> you can use the inline comment `/*!string*/` in the primary constructor parameter
315+
> type, as in `TSelf(/*!string*/ TValue Value)`.
316+
309317
In addition to constraining on the `TValue` type, you can also constrain on the
310318
the struct id/`TSelf` itself by declaring the inheritance requirements in a partial
311319
class of `TSelf` in the template. For example, the following (built-in) template
312-
ensures it's only applied/expanded for struct ids whose `TValue` is [Ulid](https://github.com/Cysharp/Ulid)
313-
and implement `INewable<TSelf, Ulid>`. Its usefulness in this case is that
314-
the given interface constraint allows us to use the `TSelf.New(Ulid)` static interface
320+
ensures it's only applied to struct ids whose `TValue` is [Ulid](https://github.com/Cysharp/Ulid)
321+
and implement `INewable<TSelf, Ulid>`. This is useful in this case since the given
322+
interface constraint allows us to use the `TSelf.New(Ulid)` static interface
315323
factory method and have it recognized by the C# compiler as valid code as part of the
316-
implementation of the parameterless `New()` factory method:
324+
implementation of introduced parameterless `New()` factory method provided by the template:
317325

318326
```csharp
319327
[TStructId]
@@ -329,8 +337,8 @@ file partial record struct TSelf : INewable<TSelf, Ulid>
329337
}
330338
```
331339

332-
> NOTE: the built-in templates will always provide an implementation of
333-
> `INewable<TSelf, TValue>`.
340+
> NOTE: the built-in templates will always emit an implementation of
341+
> `INewable<TSelf, TValue>` for all struct ids.
334342
335343
Here you can see that the constraint that the value type must be `Ulid` is enforced by
336344
the `TValue` constructor parameter type, while the interface constraint in the partial

src/StructId.Analyzer/DapperExtensions.sbn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,25 @@ public static partial class DapperExtensions
2020
/// </summary>
2121
public static TConnection UseStructId<TConnection>(this TConnection connection) where TConnection : IDbConnection
2222
{
23+
// Built-in supported TValues
2324
{{~ for id in Ids ~}}
2425
if (!SqlMapper.HasTypeHandler(typeof({{ id.TSelf }})))
2526
SqlMapper.AddTypeHandler(new DapperTypeHandler{{ id.TValue }}<{{ id.TSelf }}>());
2627

2728
{{~ end ~}}
29+
// Custom TValue handlers via pass-through type handler for the struct id
2830
{{~ for id in CustomIds ~}}
2931
if (!SqlMapper.HasTypeHandler(typeof({{ id.TSelf }})))
3032
SqlMapper.AddTypeHandler(new DapperTypeHandler<{{ id.TSelf }}, {{ id.TValue }}, {{ id.THandler }}>());
3133

3234
{{~ end ~}}
35+
// Custom TValue handlers that may not be used in struct ids at all
3336
{{~ for handler in CustomValues ~}}
3437
if (!SqlMapper.HasTypeHandler(typeof({{ handler.TValue }})))
3538
SqlMapper.AddTypeHandler(new {{ handler.THandler }}());
3639

3740
{{~ end ~}}
41+
// Templatized TValue handlers
3842
{{~ for handler in TemplatizedValueHandlers ~}}
3943
if (!SqlMapper.HasTypeHandler(typeof({{ handler.TValue }})))
4044
SqlMapper.AddTypeHandler(new {{ handler.THandler }}());

src/StructId.Analyzer/DapperGenerator.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ protected override IncrementalValuesProvider<TemplateArgs> OnInitialize(Incremen
3131

3232
var builtInHandled = source.Where(x => IsBuiltIn(x.TValue.ToFullName()));
3333

34+
// Any type in the compilation that inherits from Dapper.SqlMapper.TypeHandler<T> is also picked up,
35+
// unless its a value template
3436
var customHandlers = context.CompilationProvider
3537
.SelectMany((x, _) => x.Assembly.GetAllTypes().OfType<INamedTypeSymbol>())
3638
.Combine(context.CompilationProvider.Select((x, _) => x.GetTypeByMetadataName("Dapper.SqlMapper+TypeHandler`1")))
@@ -41,17 +43,21 @@ protected override IncrementalValuesProvider<TemplateArgs> OnInitialize(Incremen
4143
.Select((x, _) => x.Left)
4244
.Collect();
4345

46+
// Non built-in value types can be templatized by using [TValue] templates. These would necessarily be
47+
// file-local types which are not registered as handlers themselves but applied to each struct id TValue in turn.
4448
var templatizedValues = context.SelectTemplatizedValues()
4549
.Where(x => !IsBuiltIn(x.TValue.ToFullName()))
4650
.Combine(context.CompilationProvider.Select((x, _) => x.GetTypeByMetadataName("Dapper.SqlMapper+TypeHandler`1")))
4751
.Where(x => x.Left.Template.TTemplate.Is(x.Right))
4852
.Select((x, _) => x.Left);
4953

54+
// If there are custom type handlers for value types that are in turn used in struct ids, we need to register them
55+
// as handlers that pass-through to the value handler itself.
5056
var customHandled = source
5157
.Combine(customHandlers.Combine(templatizedValues.Collect()))
5258
.Select((x, _) =>
5359
{
54-
(TemplateArgs args, (ImmutableArray<INamedTypeSymbol> handlers, ImmutableArray<TValueTemplate> templatized)) = x;
60+
(TemplateArgs args, (ImmutableArray<INamedTypeSymbol> handlers, ImmutableArray<TemplatizedTValue> templatized)) = x;
5561

5662
var handlerType = args.ReferenceType.Construct(args.TValue);
5763
var handler = handlers.FirstOrDefault(x => x.Is(handlerType, false));
@@ -84,7 +90,7 @@ protected override IncrementalValuesProvider<TemplateArgs> OnInitialize(Incremen
8490
return source.Where(x => false);
8591
}
8692

87-
void GenerateHandlers(SourceProductionContext context, ((ImmutableArray<TemplateArgs> builtInHandled, ImmutableArray<TemplateArgs> customHandled), ImmutableArray<TValueTemplate> templatizedValues) source)
93+
void GenerateHandlers(SourceProductionContext context, ((ImmutableArray<TemplateArgs> builtInHandled, ImmutableArray<TemplateArgs> customHandled), ImmutableArray<TemplatizedTValue> templatizedValues) source)
8894
{
8995
var ((builtInHandled, customHandled), templatizedValues) = source;
9096
if (builtInHandled.Length == 0 && customHandled.Length == 0 && templatizedValues.Length == 0)
@@ -133,7 +139,7 @@ record ValueHandlerModel(string TValue, string THandler);
133139

134140
class ValueHandlerModelCode
135141
{
136-
public ValueHandlerModelCode(TValueTemplate template)
142+
public ValueHandlerModelCode(TemplatizedTValue template)
137143
{
138144
var declaration = template.Template.Syntax.ApplyValue(template.TValue)
139145
.DescendantNodes()

src/StructId.Analyzer/EntityFrameworkGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ protected override SyntaxNode SelectTemplate(TemplateArgs args)
8181
return idTemplate ??= CodeTemplate.Parse(ThisAssembly.Resources.Templates.EntityFramework.Text, args.KnownTypes.Compilation.GetParseOptions());
8282
}
8383

84-
void GenerateValueSelector(SourceProductionContext context, ((ImmutableArray<TemplateArgs>, ImmutableArray<INamedTypeSymbol>), ImmutableArray<TValueTemplate>) args)
84+
void GenerateValueSelector(SourceProductionContext context, ((ImmutableArray<TemplateArgs>, ImmutableArray<INamedTypeSymbol>), ImmutableArray<TemplatizedTValue>) args)
8585
{
8686
((var structIds, var customConverters), var templatizedConverters) = args;
8787

@@ -119,7 +119,7 @@ record ConverterModel(string TModel, string TProvider, string TConverter);
119119

120120
class TemplatizedModel
121121
{
122-
public TemplatizedModel(TValueTemplate template)
122+
public TemplatizedModel(TemplatizedTValue template)
123123
{
124124
var declaration = template.Template.Syntax.ApplyValue(template.TValue)
125125
.DescendantNodes()

src/StructId.Analyzer/TemplatedGenerator.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,41 @@ namespace StructId;
1212
public partial class TemplatedGenerator : IIncrementalGenerator
1313
{
1414
/// <summary>
15-
/// Represents a template for struct ids.
15+
/// Represents an instantiation of a struct id template for a specific combination
16+
/// of a <paramref name="TSelf"/> and <paramref name="TValue"/>.
1617
/// </summary>
17-
/// <param name="StructId">The struct id type, either IStructId or IStructId{T}.</param>
18+
/// <param name="TSelf">The struct id type, either IStructId or IStructId{T}.</param>
1819
/// <param name="TValue">The type of value the struct id holds, such as Guid or string.</param>
1920
/// <param name="Template">The template to apply to it.</param>
20-
record IdTemplate(INamedTypeSymbol StructId, INamedTypeSymbol TValue, Template Template);
21+
record TemplatizedStructId(INamedTypeSymbol TSelf, INamedTypeSymbol TValue, Template Template);
2122

23+
/// <summary>
24+
/// Represents the template that will be applied to a struct id.
25+
/// </summary>
26+
/// <param name="TSelf">Declaration and potential constraints to check on struct ids for the template to apply.</param>
27+
/// <param name="TValue">Target value type, potentially containing a file-local declaration with further constraints to apply.</param>
28+
/// <param name="Attribute">The <c>[TStructId]</c> attribute applied to the <paramref name="TSelf"/>.</param>
29+
/// <param name="KnownTypes">Useful known compilation types used at template expansion time.</param>
2230
record Template(INamedTypeSymbol TSelf, INamedTypeSymbol TValue, AttributeData Attribute, KnownTypes KnownTypes)
2331
{
32+
/// <summary>
33+
/// Originally declared TValue type in the primary constructor itself.
34+
/// </summary>
2435
public INamedTypeSymbol? OriginalTValue { get; init; }
2536

26-
// A custom TValue is a file-local type declaration.
37+
/// <summary>
38+
/// Whether the a custom TValue is a file-local type declaration providing further constraints.
39+
/// </summary>
2740
public bool IsLocalTValue => OriginalTValue?.IsFileLocal == true;
2841

42+
/// <summary>
43+
/// The syntax tree root of the template file.
44+
/// </summary>
2945
public SyntaxNode Syntax { get; } = TSelf.DeclaringSyntaxReferences[0].GetSyntax().SyntaxTree.GetRoot();
3046

47+
/// <summary>
48+
/// Whether the template should not be applied to string value types.
49+
/// </summary>
3150
public bool NoString { get; } = new NoStringSyntaxWalker().Accept(
3251
TSelf.DeclaringSyntaxReferences[0].GetSyntax().SyntaxTree.GetRoot());
3352

@@ -131,18 +150,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
131150
// If the TValue/Value implements or inherits from the template base type and/or its interfaces
132151
return templates
133152
.Where(template => template.AppliesTo(tid))
134-
.Select(template => new IdTemplate(id, tid, template));
153+
.Select(template => new TemplatizedStructId(id, tid, template));
135154
});
136155

137156
context.RegisterSourceOutput(ids, GenerateCode);
138157
}
139158

140-
void GenerateCode(SourceProductionContext context, IdTemplate source)
159+
void GenerateCode(SourceProductionContext context, TemplatizedStructId source)
141160
{
142161
var templateFile = Path.GetFileNameWithoutExtension(source.Template.Syntax.SyntaxTree.FilePath);
143-
var hintName = $"{source.StructId.ToFileName()}/{templateFile}.cs";
162+
var hintName = $"{source.TSelf.ToFileName()}/{templateFile}.cs";
144163

145-
var applied = source.Template.Syntax.Apply(source.StructId);
164+
var applied = source.Template.Syntax.Apply(source.TSelf);
146165
var output = applied.ToFullString();
147166

148167
context.AddSource(hintName, SourceText.From(output, Encoding.UTF8));

src/StructId.Analyzer/TValueTemplateExtensions.cs renamed to src/StructId.Analyzer/TemplatizedTValueExtensions.cs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
namespace StructId;
77

88
/// <summary>
9-
/// Represents a template for the value type of struct ids.
9+
/// Represents a templatized value type of a struct ids.
1010
/// </summary>
11-
/// <param name="TValue">The type of value the struct id holds, such as Guid or string.</param>
11+
/// <param name="TValue">The type of value the a struct id holds, such as Guid or string.</param>
1212
/// <param name="Template">The template to apply to it.</param>
13-
record TValueTemplate(INamedTypeSymbol TValue, TValueTemplateInfo Template)
13+
record TemplatizedTValue(INamedTypeSymbol TValue, TValueTemplate Template)
1414
{
1515
SyntaxNode? applied;
1616

@@ -26,10 +26,26 @@ record TValueTemplate(INamedTypeSymbol TValue, TValueTemplateInfo Template)
2626
public string Render() => Declaration.ToFullString();
2727
}
2828

29-
record TValueTemplateInfo(INamedTypeSymbol TTemplate, KnownTypes KnownTypes)
29+
/// <summary>
30+
/// Represents a generic file-local template that applies to TValues that match the template
31+
/// constraints.
32+
/// </summary>
33+
/// <param name="TTemplate">The declared symbol of the template in the compilation.</param>
34+
/// <param name="KnownTypes">Useful known types for use when applying the template.</param>
35+
record TValueTemplate(INamedTypeSymbol TTemplate, KnownTypes KnownTypes)
3036
{
37+
/// <summary>
38+
/// Syntax root of the file declaring the template.
39+
/// </summary>
3140
public SyntaxNode Syntax { get; } = TTemplate.DeclaringSyntaxReferences[0].GetSyntax().SyntaxTree.GetRoot();
3241

42+
/// <summary>
43+
/// Whether the template should not be applied to string value types.
44+
/// </summary>
45+
/// <remarks>
46+
/// Since strings implement also a bunch of interfaces, an easy way to exclude them
47+
/// from matching a struct value template that has a restriction on just
48+
/// </remarks>
3349
public bool NoString { get; } = new NoStringSyntaxWalker().Accept(
3450
TTemplate.DeclaringSyntaxReferences[0].GetSyntax().SyntaxTree.GetRoot());
3551

@@ -66,9 +82,12 @@ public bool AppliesTo(INamedTypeSymbol valueType)
6682
}
6783
}
6884

69-
static class TValueTemplateExtensions
85+
static class TemplatizedTValueExtensions
7086
{
71-
public static IncrementalValuesProvider<TValueTemplate> SelectTemplatizedValues(this IncrementalGeneratorInitializationContext context)
87+
/// <summary>
88+
/// Gets all instantiations of TValue templates that apply to the struct ids in the compilation.
89+
/// </summary>
90+
public static IncrementalValuesProvider<TemplatizedTValue> SelectTemplatizedValues(this IncrementalGeneratorInitializationContext context)
7291
{
7392
var structIdNamespace = context.AnalyzerConfigOptionsProvider.GetStructIdNamespace();
7493

@@ -87,7 +106,7 @@ public static IncrementalValuesProvider<TValueTemplate> SelectTemplatizedValues(
87106
r => r.GetSyntax() is TypeDeclarationSyntax declaration && x.GetAttributes().Any(
88107
a => a.IsValueTemplate())))
89108
.Combine(known)
90-
.Select((x, cancellation) => new TValueTemplateInfo(x.Left, x.Right))
109+
.Select((x, cancellation) => new TValueTemplate(x.Left, x.Right))
91110
.Collect();
92111

93112
var values = context.CompilationProvider
@@ -104,20 +123,9 @@ public static IncrementalValuesProvider<TValueTemplate> SelectTemplatizedValues(
104123
var tvalue = (INamedTypeSymbol)structId.TypeArguments[0];
105124
return templates
106125
.Where(template => template.AppliesTo(tvalue))
107-
.Select(template => new TValueTemplate(tvalue, template));
126+
.Select(template => new TemplatizedTValue(tvalue, template));
108127
});
109128

110129
return values;
111130
}
112-
113-
//void GenerateCode(SourceProductionContext context, TIdTemplate source)
114-
//{
115-
// var templateFile = Path.GetFileNameWithoutExtension(source.Template.Syntax.SyntaxTree.FilePath);
116-
// var hintName = $"{source.TValue.ToFileName()}/{templateFile}.cs";
117-
118-
// var applied = source.Template.Syntax.Apply(source.TValue);
119-
// var output = applied.ToFullString();
120-
121-
// context.AddSource(hintName, SourceText.From(output, Encoding.UTF8));
122-
//}
123131
}

src/StructId/Templates/DapperTypeHandler.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Diagnostics.CodeAnalysis;
77
using StructId;
88

9-
// TODO: pending making it conditionally included at compile-time
109
[TValue]
1110
file class TValue_TypeHandler : Dapper.SqlMapper.TypeHandler<TValue>
1211
{

src/StructId/Templates/ParsableT.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? prov
2525
}
2626

2727
// This will be removed when applying the template to each user-defined struct id.
28-
file record struct TValue : IParsable<TValue>
28+
file struct TValue : IParsable<TValue>
2929
{
3030
public static TValue Parse(string s, IFormatProvider? provider) => throw new NotImplementedException();
3131
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TValue result) => throw new NotImplementedException();

src/StructId/Templates/SpanParsable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ file partial record struct TSelf
3232
}
3333

3434
// This will be removed when applying the template to each user-defined struct id.
35-
file record struct TValue : ISpanParsable<TValue>
35+
file struct TValue : ISpanParsable<TValue>
3636
{
3737
public static TValue Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => throw new NotImplementedException();
3838
public static TValue Parse(string s, IFormatProvider? provider) => throw new NotImplementedException();

0 commit comments

Comments
 (0)