A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/dotnet/csharplang/issues/3772 below:

Source Generator Safe Editions(Transformer) · Issue #3772 · dotnet/csharplang · GitHub

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).

Issues related to Editing

Allow editing source code comes with a lot of headaches, here some of them:

  1. If multiple generators edit the same file, in what order they must run???, what input get each one???
  2. Performance on IDE may be poor since the current proposal states that generators run unordered.
  3. How to offer high fidelity Intellisense support for the modified Types.
  4. (The great one) Allow editions may be a great source for anti-patters and might create a lot of c# dialects destroying the "what you see is what you get" principle.
Solutions

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.

Implementation

Each 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:

  1. The composition is not defined in the transformer, so there is not user-code intervention here.
  2. The non-API changes rule allows all the local functions to have the same signature that the method been transformed without issues.
  3. All the nested local functions are named: 'original'. In this way, each transformer only has access to the output of the previous transformer and to the method been transformed.
  4. Since all the nested functions have the same signature, all parameters have the same names and there for only the inner-most are accessible to each function body.
  5. The transformer code is always inserted inside a block after the 'original' nested function, this allows that any user defined variable doesn't exist inside the corresponding original scope. (Is not captured by the closure)
  6. Each transformer gets the same methodInformation instance as input, so they can run unordered or even in parallel, the order is only needed to apply composition.
  7. The composition operator is a constant operation since it only adds the output of the previous transformer as a local definition.
Conclusions

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