Skip to content
This repository has been archived by the owner on Apr 18, 2020. It is now read-only.

Injecting data into generators #21

Open
DavidDudson opened this issue Dec 20, 2017 · 5 comments
Open

Injecting data into generators #21

DavidDudson opened this issue Dec 20, 2017 · 5 comments
Milestone

Comments

@DavidDudson
Copy link
Member

DavidDudson commented Dec 20, 2017

Take for example the following...

What the author wants to write is:

package foo.bar

case class PrintThisString(s: String) extends ExtensionGenerator {
  def extend(c: Defn.Class): List[Stat] = {
     q"println(${Lit.String(s)})"
  }
}

That way, the user writing code which uses the generator, writes valid code as far as the IDE is concerned.

We need a way of injecting foo into the class.
We also want to create an instance of this thing, that does not have the parameters supplied.

What we will end up needing (with semantics) is the following

package foo.bar

case class PrintThisString() extends ExtensionGenerator(t"foo.bar.PrintThisString") {
  var foo: String

  def extend(c: Defn.Class): List[Stat] = {
     q"println(${Lit.String(s)})"
  }
}

This is not the only information we need to inject either.
Things like a semantic database, will be required in the future.

Theres a few of ways we could do this....

  1. Mirror Elysium
    Have an annotation of some kind
package foo.bar

@ParameterGenerator
case class PrintThisString(s: String) extends ExtensionGenerator {
  def extend(c: Defn.Class): List[Stat] = {
     q"println(${Lit.String(s)})"
  }
}
  1. Mirror scalameta/paradise
package foo.bar

case class PrintThisString(s: String) extends ExtensionGenerator {
  def extend(c: Defn.Class): List[Stat] = {
     q"case class PrintThisString(${s: Lit.String})" = this
     q"println(${s})"
  }
}
  1. Hide the behaviour from the user.

Effectively the same as 1. Except without an annotation.

Before executing the generator code, we pass over a generator to transform the class into something more usable from the generators perspective.

So scalagen actually transforms generator sources, and actual sources.

I prefer 3. So that's what I'm going to explore, any objections should be discussed here.

@olafurpg
Copy link
Member

How about

  def extend(c: Defn.Class, ctx: Context): List[Stat] = {
    ctx.prefix/enclosingClass/parameters/semanticdb
  }

This is probably the easiest option with regards to classloading and is also quite similar to scala-reflect macros. I never like this in scalameta/paradise and in scalacenter/macros I've moved closer to a scala-reflect compatible interface.

@DavidDudson
Copy link
Member Author

@olafurpg For "Universe" like behaviour I agree that we should use context. perhaps even make it implicit.

For parameters specifically, they are named, and on the class that is the generator.

It seems odd for an author to have to write ctx.parameters.get("foo") or similar in order to access a field that is sitting right in the constructor of the class.

For example...

case class Bar(foo: String) {
  def extend(c: Defn.Class, ctx: Context): List[Stat] = {
    ctx.parameters.get("foo")
  }
}

The above seems really peculiar, not to mention that we would have to use HLists or similar for the args in order to get the right types. I think code generation has value here. (Even if it is slightly more magic).

@olafurpg
Copy link
Member

The arguments to the annotation are not necessarily literals, they can be any syntax.
The way I would do it is to separate the annotation from the expansion method

class Foo(arg: String) extends StaticAnnotation
object Foo extends Generator {
  def extend(defn: Defn.Class, c: Context): List[Stat] = {
    val Lit.String(arg) = c.prefix
    ...
  }
}

I think code generation has value here. (Even if it is slightly more magic).

I would personally start with something more boilerplatey to begin with, and then once the common pattern emerges we can discuss how to improve it. scalagen may be one solution, but shapeless or a custom def macro might also work fine.

@DavidDudson
Copy link
Member Author

Thats fair. Although I think we will want to provide helper methods that provide clearer error messages rather then just a match error. It's a common enough pattern. I see literals used more then any other parameters.

@olafurpg
Copy link
Member

provide helper methods that provide clearer error messages rather then just a match error

Absolutely! I never liked the val Lit.String(x) = ... pattern

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants