scalameta / scalagen

WIP - Scalameta powered code generation
Apache License 2.0
40 stars 5 forks source link

Unify Generator API's #44

Open DavidDudson opened 6 years ago

DavidDudson commented 6 years ago

The whole split abstract class for different generators is nice in theory, but in reality, it is just a pain.

I have a proof of concept idea which is nearly complete.

For this to be performant, my latest changes to scalameta contrib will need to be pulled in, but this is not an issue at present.

Current

  1. Discover generators
  2. Match on generator type
  3. Apply based on Input Tree Type

Proposed

  1. Discover Generators
  2. Match on Input tree type.
  3. Apply ALL generators
  4. Abort or skip if generator incompatibility is detected

How it works

  1. My recent changes to scalameta contrib short circuit structural equality when the object is identical.
  2. I've added a method to scalagen like withStats that can short-circuit
    • I'm undecided on whether we want this to be the default as it requires both Extract and Replace instances present, which is a large binary incompatibility.
  3. It runs all generators on the input tree, in parallel.
  4. It then checks if more then one of the incompatible generators modified the tree. (aka is not identical to the source)
  5. If it did not abort, it will apply the transformations.

Incompatibilties

E = Extension CE = CompanionExtension M = Manipulation T = Transmutation P = Param

_ E CE M T P
E _ X X
CE _
M X _ X
T X X _
P _

As you can see, You cannot extend and manipulate the same Tree. You can, however, replicate this behavior by choosing the order as a generator author. By calling directly into the relevant method.

Examples

// Abort - tried to manipulate class twice
class Foo extends Generator("Foo") {
  def manipulate(c: Defn.Class): Defn.Class = ???
  def extend(c: Defn.Class): List[Stat] = ???
}
// Success - different tree types are fine
class Foo extends Generator("Foo") {
  def manipulate(c: Defn.Class): Defn.Class = ???
  def extend(c: Defn.Trait): List[Stat] = ???
}
// Success - no conflict as the Companion and class are different objects
class Foo extends Generator("Foo") {
  def extendCompanion(c: Defn.Class): List[Stat] = ???
  def extend(c: Defn.Class): List[Stat] = ???
}
DavidDudson commented 6 years ago

@olafurpg Do you like this better than the current API?

DavidDudson commented 6 years ago

Blocked by #24