The following proposal generalize the concept of source generators that is being implemented to allow safe editions on source code. It enables some scenarios that where posible on the original proposal (the ones with replace and original keywords in the language) without adding the implementation complexity of it.
The current design of source generators is additive only and it may receive what the team call IDE Integration, understood as design time generation, the ones that doesn't receive this "IDE Integration" only runs at build time.
This proposal is about providing an extension point around the following interface to enable additive only mutations.
public interface ITransformer<T> where T:Attribute { BlockSyntax Transform(MethodInformation method,AttributeInformation att); }Why allow editions?
AOP and meta-programming (for more info on why allow editions read this blog
from Gael Fraiteur, the creator of PostSharp).
Allow editing source code comes with a lot of headaches, here some of them:
Allow only non-API changes non-API changes mean disallow any changes on definitions. Having this restriction is imposible to modify a method signature, or even a type definition.
Only changes to code blocks are allowed, and in C# this means methods and properties' body. (For simplification only methods' body transformers are explained here)
With non-API changes the interface of a type won't change and that also means that the Intellisense support for that type remains invariant after the edition.
(This solves problem 3).
Issues 1 and 2 are focused on performance to allow a great experience on the IDE. An important thing to notice is that there is not need to run the edit generators in design time thanks to the non-API changes rule and that is, in fact, very good for the IDE experience.
ImplementationEach transformer must use an attribute to mark which method body is transformed. So multiple editions on a method are seen as multiple attributes on the method definition.
Composition order is implicitly defined by the attributes:
[Timer] [Trace] public void fibonacci(int n)=> n<2?1:fibonacci(n-1)+fibonacci(n-2);
The compose order in the example is first apply Trace and then Timer.
Lets define TraceTransformer for the explanation:
//The attribute needed to use the transformer public class TraceAttribute: Attribute{} public class TraceTransformer: ITransformer<TraceAttribute> { public BlockSyntax Transform(MethodInformation original, AttributeInformation attinfo) { return SyntaxFactory.ParseBlock($@"{{ Console.WriteLine(""{original.Name}""+"" started...""); var result=original({original.Arguments.First().Name}); Console.WriteLine({original}+"" ended...""); }}"); } }
And also TimerTransformer:
//The attribute needed to use the transformer public class TimerAttribute: Attribute{} public class TimerTransformer: ITransformer<TimerAttribute> { public BlockSyntax Transform(MethodInformation method,AttributeInformation att) { return SyntaxFactory.ParseBlock($@"{{ var crono = new Stopwatch(); crono.Start(); var result=original({method.Arguments.First().Name}); crono.Stop(); Console.WriteLine({method.Name}+"" runs in ""+crono.ElapsedMilis+"" milliseconds""); }}"); } }
The result of the composition operation on the previously defined fibonacci function is:
int fibonacci(int n) { int original(int n) { int original(int n) => n<2?1:fibonacci(n-1)+fibonacci(n-2); { Console.WriteLine("fibonacci"+" started..."); var result = original(n); Console.WriteLine("fibonacci"+" ended..."); return result; } } { Stopwatch crono = new Stopwatch(); crono.Start(); var result = original(n); crono.Stop(); Console.WriteLine("fibonacci"+" runs in "+crono.ElapsedMilis+" milliseconds."); return result; } }
The composition operation consists on defining a local function with the same signature that the method been transformed and with the body of the
previous transformer or in the base case the body of the method been transformed, but with name "original".
Important things to notice:
This proposal may comply with most of common arguments against allowing source code editing at build time (source code weaving) in C#. Also it uses previous C# features, without need to add new keywords or constructs. It fits or complements C# 9's proposal additive source generators.
Current proposal no complies with issue 4. Maybe because, there is not answer to solve it... it doesn't allow any transformation that breaks the principle of "what you see is what you get" (even when C# compiler does some magic in several scenarios), then this current proposal of source generators isn't allowed under that rule.
Also, it's important to remark that allowing transformers doesn't change the semantic of the language because it's true that it mutates source code, but it is an additive only mutation, so any error that exists before the transformation process exists also after it runs.
HamedFathi, sighoya, jyeros, alexfertel, hectorfmasson and 29 more
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4