Add Template Generator#1
Conversation
|
While there are still some TODOs to be resolved (hence the https://github.com/ChaosTechnology/ChaosGenerators/labels/draft label) this should present the basic concept. So, please take a look and tell me what you think. The above mentioned math methods can be implemented like this: /// <summary> Returns the greatest of the given values. </summary>
/// <param name="a"> First value to be compared. </param>
/// <param name="b"> Second value to be compared. </param>
[TemplateGenerator(
"=> a > b ? a : b;",
typeof(sbyte), typeof(byte),
typeof(short), typeof(ushort),
typeof(int), typeof(uint),
typeof(long), typeof(ulong),
typeof(float), typeof(double), typeof(decimal)
)]
public delegate T _Max<T>(T a, T b); |
FramePerfection
left a comment
There was a problem hiding this comment.
The general approach is looking good so far.
I'm not sure where exactly in the "build hierarchy" I'd see this generator concept (or this generator in particular), but since it depends on L1 libraries, I don't think it should be outside of the scope of the hierarchy system like ChaosPresets and ChaosReferences are.
My intuition says that this is L2 - ChaosBuild, as any generator is a necessary part of the build chain and invoked by the build tools, but there may very well be technical reasons against putting "general" generators in that layer. They are, after all, quite different from build tasks.
| return; | ||
| } | ||
|
|
||
| string templateMethodName = templateFileName.TrimStart('_'); |
There was a problem hiding this comment.
This TrimStart makes it possible to create templates that will generate code that will not compile, particularly by using the same template name with a different amount of underscores, e.g. delegate int _Test<T>() and delegate int __Test<T>().
It may be better to demand an exact number of underscores or some other exact prefix to prevent this particular issue.
There was a problem hiding this comment.
The ability to produce methods with the same name is the exact reason why a variable number of underscores (or any other distinction really) is necessary. After all two delegates may not share the same name, even if their parameters differ.
E.g. Max:
/// <summary> Returns the greatest of the given values. </summary>
/// ...
[TemplateGenerator(
"=> a > b ? a : b;",
typeof(sbyte), typeof(byte), ...
)]
public delegate T __Max<T>(T a, T b);and
/// <summary> Returns the greatest of the given values. </summary>
/// ...
[TemplateGenerator(
"{\n foreach (T b in v)\n v0 = Max(v0, b);\n return v0;\n}",
typeof(sbyte), typeof(byte), ...
)]
public delegate T ___Max<T>(T v0, params T[] v);And yes, obviously you can generate code that will not compile.
Just like you can write such code by hand...
| code.AppendLine(Indent(--indent, "}")); | ||
|
|
||
| context.AddSource( | ||
| $"{containingType.FullName()}.{templateFileName}.cs", |
There was a problem hiding this comment.
It is possible to raise an InvalidArgumentException here by declaring different overloads for delegates with the TemplateGenerator attribute and the same name. This should be reported as a diagnostic instead, I think.
I don't really understand why I'm still getting the exception in the build log when I wrap it in a try/catch block, however.
There was a problem hiding this comment.
Correct, this should raise an error.
For why you still get the exception...
- some other issue may have prevented the generator from recompiling
- the old binary may have been cached somewhere
Both happened a couple times for me.
In that case
- Close the IDE
- Delete the build folders for the generator and the project referencing it
- Restart the IDE
There was a problem hiding this comment.
Took care of that: c44c359
No error. Instead a unique file name as both of the following produce valid non conflicting implemenations.
internal delegate System.Tuple<T1, T2> _Test<T1, T2>(ref T1 v1, ref readonly T2 v2);internal delegate System.Tuple<T1, T2, T3> _Test<T1, T2, T3>(ref T1 v1, ref readonly T2 v2, params T3[] v3);|
|
||
| static string GetTypeName(ITypeSymbol type) | ||
| { | ||
| // TODO: Move to ChaosAnalyzers? |
|
It may be nice to have a constructor for the |
| for (int overload = 0; overload < numOverloads; overload++) | ||
| { | ||
| ((Regex, string), ITypeSymbol)[] result = new ((Regex, string), ITypeSymbol)[templateParameters.Length]; | ||
| for (int i = 0; i < templateParameters.Length; i++) | ||
| result[i] = (templateParameters[i], templateArgumentTypes[overload * templateParameters.Length + i]); | ||
|
|
||
| yield return result; | ||
| } |
There was a problem hiding this comment.
This loop should check for duplicate template argument combinations (i.e. duplicate overloads it would be generating) and raise an error diagnostic in that case.
There was a problem hiding this comment.
I guess a warning would suffice, as detecting and skipping such a duplicate would not break anything. But valid point.
…plate would be generated multiple times
I placed it in L1 in my test environment, as for MSBuild a generator and an analyzer are essentially the same. As long as a specific generator does not depend on a higher level library, there's no need to place it anywhere else. |
…ad-duplicates # Conflicts: # ChaosGenerators/source/TemplateGenerator.cs
…ad-duplicates-review diagnose generating overload duplicates - review
…ad-duplicates Report error diagnostic when a specific overload from a single template would be generated multiple times
Template Generator
This source generator allows generating multiple overloads for a method by providing its signature and a generic implementation, similar to the concept of C++ templates.
The goal of this generator is to drastically reduce copy paste code and documentation for methods that can't be easily implemented using normal generics, like methods that need to use arithmetic or logical operators, which cannot be required by generic type constraints, like in ChaosFramework.Math's Min, Max or Clamp methods.
How to use
Define the signature of the method to be generated by declaring a generic delegate. The generic parameters of the delegate will serve as template type parameters. Then decorate the delegate with a TemplateGeneratorAttribute. The first argument of the attribute constructor is the string to be used for the implementation. The second argument specifies the types (or type combinations) for which to implement the template. Within the implementation string occurences of the generic type parameters' names will be replaced by the actual types for which to implement the template. It is possible to generate generic templates by providing
nullfor specific template type parameters.Example
The following code declares a template method named
Test:This results in the following code being generated: