Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Symbol.newClass is not flexible enough #16128

Open
pshirshov opened this issue Sep 30, 2022 · 0 comments
Open

Symbol.newClass is not flexible enough #16128

pshirshov opened this issue Sep 30, 2022 · 0 comments
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:enhancement

Comments

@pshirshov
Copy link
Contributor

pshirshov commented Sep 30, 2022

I'm trying to build a macro which, for given trait T would generate an implementation:

// I have this
trait T {
  def a: Int
}

// I need to generate this
class TImpl(override val a: Int) extends T {}

It seems that the only way to create a Symbol for a class is Symbol.newClass method, which addes an empty constructor into the newly created Symbol. Also it looks like there is no way to remove that constructor.

So, I'm able to build a tree which looks exactly as one generated by the compiler with the following crappy code:

    val name: String = s"${sym.name}AutoImpl"

    val parents = if (isAbstract){
      List(tree)
    } else {
      List(TypeTree.of[Object], tree)
    }


    def mkConstr(cls: Symbol) = Symbol.newMethod(cls,
      "<init>",
      MethodType(abstractMethods.map(_.symbol.name))(_ => abstractMethods.map(_.returnTpt.symbol.typeRef), _ => cls.typeRef), Flags.Method | Flags.StableRealizable, Symbol.noSymbol)
    def decls(cls: Symbol): List[Symbol] = List(mkConstr(cls))
    val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), (cls: Symbol) => decls(cls), selfType = None)


    def vals(f: Flags) = abstractMethods.map {
      m =>
        Symbol.newVal(cls, m.symbol.name, m.returnTpt.symbol.typeRef, f, Symbol.noSymbol).tree.asInstanceOf[ValDef]
    }
    val clsDef = ClassDef(cls, parents, body =  vals(Flags.Override | Flags.ParamAccessor))


    val consDef = DefDef(mkConstr(cls), argss => None)
    val consDef2 = DefDef.copy(consDef)(consDef.name, List(TermParamClause(vals(Flags.Param))), consDef.returnTpt, None)


    val clsDef2 = ClassDef.copy(clsDef)(name, consDef2, parents, None, vals(Flags.Override | Flags.ParamAccessor))

The tree generated by the compiler looks like

TypeDef(TImpl,
Template(
  DefDef(
      <init>,
      List(List(ValDef(toWire,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree))),
      TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class izumi)),class TImpl)],
      EmptyTree
    ),
    List(
      TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object)], TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class izumi)),trait T)]
    ),
    ValDef(_,EmptyTree,EmptyTree),
    List(ValDef(toWire,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree))
))

and the tree generated by my macro looks like

TypeDef(
  TAutoImpl,
  Template(
  DefDef(
    <init>,
    List(List(ValDef(toWire,TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Int)],EmptyTree))),
    TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class izumi)),trait T)],
    EmptyTree
  ),
  List(
    TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),Object)], 
    TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class izumi)),trait T)]
  ),
  ValDef(_,EmptyTree,EmptyTree),
  List(ValDef(toWire,TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Int)],EmptyTree))
))

So, visually the trees are identical.

Though .show for my tree produces class TAutoImpl(toWire: scala.Int) extends java.lang.Object with izumi.T instead of expected class TAutoImpl(override val toWire: scala.Int). I'm not sure why exactly this happens, but I guess it's somehow related to the empty constructor added by newClass.

I think the API should be improved. It may be more ergonomic and it must be possible to define trees for arbitrary classes.

Another construction which I've been unable to reproduce in a macro was an implementation for an abstract class passing a constructor parameter to parent's constructor:

// I have this
abstract class AnAbstractClass(c: Int) {
  def toWire: String
//  def bullshit(a: Int): String
  def xxx: String = ""
}
// need to generate this:
class AClass2(cparam: Int) extends AnAbstractClass(cparam) {
  override def toWire: String = "xxx"
}
@pshirshov pshirshov added the stat:needs triage Every issue needs to have an "area" and "itype" label label Sep 30, 2022
@dwijnand dwijnand added area:metaprogramming:reflection Issues related to the quotes reflection API itype:enhancement and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:enhancement
Projects
None yet
Development

No branches or pull requests

2 participants