scalameta / scalagen

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

[suggestion] Transform Pkg and Pkg.Object #17

Open iTakeshi opened 6 years ago

iTakeshi commented 6 years ago

Hi,

I'm very excited about this project is trying to solve many shortcomings of scala.meta-based macro annotation. One big problem of macro annotation was it couldn't expand Pkg and Pkg.Object because we can use @annotation only for Defns. However it seems highly useful if we can generate code based on Pkgs.

So, is there any way to bypass this limitation when applying scalagen generators? Or, should we request for a modification in specification of scalameta itself?

DavidDudson commented 6 years ago

Do you have examples of what you would like to accomplish?

iTakeshi commented 6 years ago

I want use this feature to generate json formatter for all case classes in a playframework application

example:

// original
@genFormatters package object formatters { // NOTE: @annotation for a package object is not allowed
  case class A(id: Int, name: String)
  case class B(id: Int, name: String, foreignId: Int)
}

// I want it be converted to
package object formatters {
  case class A(id: Int, name: String)
  object A {
    implicit val format = (
      (__ / "id").format[Int] and
      (__ / "name").format[String]
    )(A.apply _, unlift(A.unapply))
  }

  case class B(id: Int, name: String, foreignId: Int)
  object B {
    implicit val format = (
      (__ / "id").format[Int] and
      (__ / "name").format[String] and
      (__ / "foreign_id").format[Int]
    )(B.apply _, unlift(B.unpply))
  }
}

Of course I know I can annotate to case classes one by one, but it is annoying especially when a package has dozens of formatter classes.

olafurpg commented 6 years ago

The scala language spec does not permit annotations on package objects https://www.scala-lang.org/files/archive/spec/2.11/09-top-level-definitions.html#package-objects

Interestingly however, scala.meta.Pkg.Object has a field for annotations https://github.com/scalameta/scalameta/blob/d4822c8758e108bc1825c0f0f6856f86019c7262/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala#L314 but it's not used in the parser https://github.com/scalameta/scalameta/blob/d4822c8758e108bc1825c0f0f6856f86019c7262/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala#L3402

There is no requirement to only expand annotations in scalagen, we can come up with any convention we like. For example, in this json formatters case it might make sense to configure it in the build that all case classes in a given package or project should get a json formatter.

iTakeshi commented 6 years ago

I see, that's a limitation of Scala itself. I thought it came from scala.meta because Pkg.Object has mods, as you mentioned.

There is no requirement to only expand annotations in scalagen

I agree, and your suggestion that

configure it in the build that all case classes in a given package

seems nice. Should this kind of configuration be managed by a SBT plugin?

DavidDudson commented 6 years ago

Yeah, we could even explore expanding based on comments.

Provided the name is fully qualified (_root_ would not be necess thoughary)

// Apply Generator: org.scalagen.Formatting
package object formatters { // NOTE: @annotation for a package object is not allowed
  case class A(id: Int, name: String)
  case class B(

Although in that case it would be way easier to do the following:

package object formatters extends formatters
@genFormatters trait formatters {
  case class A(id: Int, name: String)
  case class B(id: Int, name: String, foreignId: Int)
}

There are plenty of other ways too, outside of annotations. We can definately explore those. The annotation based infrastructure is being written first as that is what most of the community is used to.

DavidDudson commented 6 years ago

Note: Typeclass derivation is very high on my agenda. Especially for AST code (which Json effectively is).

I plan to write derivations for Eq, Show, Traverse and others.

olafurpg commented 6 years ago

Yeah, we could even explore expanding based on comments.

I think we should use comments as a last resort. I would prefer to explore build configuration first.

iTakeshi commented 6 years ago

Yeah, we could even explore expanding based on comments.

Actually comments will give us the biggest flexibility (and personally I'm interested in this idea), but it is a kind of double-edged sword. As @olafurpg said, it's better to explore other stuff for this moment.

However, after considering for some time, I doubt that the "build configuration" is the best way, because it forces us to separate generation rules from where it should be applied. One good point of annotation is that it can literally "annotate" where to generate code, and a problem is there are not other suitable syntactic elements which can be put anywhere in code, except for comments.

olafurpg commented 6 years ago

In scalafix we use comments to suppress false positives instead of annotations precisely because it's possible to comment on any syntax. If you think comments are better then don't mind me! It seems tools like related tools like go generate use comments

DavidDudson commented 6 years ago

With more thought. I'm pretty against comments now... The point of Generators is to extend the functionality of a given tree etc. Personally, I leave comments collapsed in my editor.

An annotation is much more discoverable then a comment.

If a user is exploring an API, he's less likely to check for generator comment, then an annotation. The annotation serves as documentation to the user.

This is the opposite of scalafix. Scalafix targets the author of the code, not the user.

The further away from actual syntax a generator is, the more documentation is required to surround it.

For example:

Annotation Generators:

Comment Generators:

Build Tool Generators: