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

Liftables: extract nested defs into class scope #2851

Merged
merged 1 commit into from
Sep 13, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,37 +40,8 @@ class LiftableMacros(override val c: Context) extends AdtLiftableMacros(c) with
localName: TermName,
body: Tree
): Option[Tree] = {
// NOTE: We have this check as a special case here, in addition to requires in Trees.scala,
// because I think this is going to be a very common mistake that new users are going to make,
// so I'd like that potential mistake to receive extra attention in form of quality error reporting.
def prohibitName(pat: Tree): Tree = {
q"""
def prohibitName(pat: _root_.scala.meta.Tree): _root_.scala.Unit = {
def unquotesName(q: _root_.scala.meta.internal.trees.Quasi): Boolean = {
val tpe = q.hole.arg.tpe // NOTE: no easy way to find this out without holes
tpe != null && tpe <:< typeOf[scala.meta.Term.Name]
}
pat match {
case q: _root_.scala.meta.internal.trees.Quasi if unquotesName(q) =>
val action = if (q.rank == 0) "unquote" else "splice"
c.abort(q.pos, "can't " + action + " a name here, use a pattern instead (e.g. p\"x\")")
case _ =>
}
}
prohibitName($pat)
"""
}
// NOTE: See #277 and #405 to understand why this special-casing is necessary.
def specialcaseTermApply(body: Tree): Tree = {
def liftPath(path: String) = {
val init = q"""c.universe.Ident(c.universe.TermName("_root_"))""": Tree
path
.split('.')
.foldLeft(init)((acc, part) => q"c.universe.Select($acc, c.universe.TermName($part))")
}
def liftField(value: Tree, tpe: Tree) = {
q"_root_.scala.Predef.implicitly[c.universe.Liftable[$tpe]].apply($value)"
}
def specialcaseTermApply: Tree = {
q"""
object ApplyToTripleDots {
def unapply(tree: _root_.scala.meta.Tree): Option[(_root_.scala.meta.Term, _root_.scala.meta.Term.Quasi)] = tree match {
Expand Down Expand Up @@ -100,20 +71,50 @@ class LiftableMacros(override val c: Context) extends AdtLiftableMacros(c) with
}
"""
}
def customize(body: Tree): Option[Tree] = {
if (adt.tpe <:< QuasiSymbol.toType) Some(q"Lifts.liftQuasi($localName)")
else if (adt.tpe <:< TermApplySymbol.toType) Some(specialcaseTermApply(body))
else if (adt.tpe <:< DefnValSymbol.toType)
Some(q"{ $localName.pats.foreach(pat => ${prohibitName(q"pat")}); $body }")
else if (adt.tpe <:< DefnVarSymbol.toType)
Some(q"{ $localName.pats.foreach(pat => ${prohibitName(q"pat")}); $body }")
else if (adt.tpe <:< PatBindSymbol.toType)
Some(q"{ ${prohibitName(q"$localName.lhs")}; $body }")
else if (adt.tpe <:< PatTypedSymbol.toType)
Some(q"{ ${prohibitName(q"$localName.lhs")}; $body }")
else None
}
// NOTE: we ignore tokens here for the time being
customize(body)
if (adt.tpe <:< QuasiSymbol.toType)
Some(q"Lifts.liftQuasi($localName)")
else if (adt.tpe <:< TermApplySymbol.toType)
Some(specialcaseTermApply)
else if (adt.tpe <:< DefnValSymbol.toType)
Some(q"{ $localName.pats.foreach(pat => ${prohibitName(q"pat")}); $body }")
else if (adt.tpe <:< DefnVarSymbol.toType)
Some(q"{ $localName.pats.foreach(pat => ${prohibitName(q"pat")}); $body }")
else if (adt.tpe <:< PatBindSymbol.toType)
Some(q"{ ${prohibitName(q"$localName.lhs")}; $body }")
else if (adt.tpe <:< PatTypedSymbol.toType)
Some(q"{ ${prohibitName(q"$localName.lhs")}; $body }")
else None
}

// NOTE: We have this check as a special case here, in addition to requires in Trees.scala,
// because I think this is going to be a very common mistake that new users are going to make,
// so I'd like that potential mistake to receive extra attention in form of quality error reporting.
private def prohibitName(pat: Tree): Tree = {
q"""
def prohibitName(pat: _root_.scala.meta.Tree): _root_.scala.Unit = {
def unquotesName(q: _root_.scala.meta.internal.trees.Quasi): Boolean = {
val tpe = q.hole.arg.tpe // NOTE: no easy way to find this out without holes
tpe != null && tpe <:< typeOf[scala.meta.Term.Name]
}
pat match {
case q: _root_.scala.meta.internal.trees.Quasi if unquotesName(q) =>
val action = if (q.rank == 0) "unquote" else "splice"
c.abort(q.pos, "can't " + action + " a name here, use a pattern instead (e.g. p\"x\")")
case _ =>
}
}
prohibitName($pat)
"""
}

private def liftPath(path: String): Tree = {
val init = q"""c.universe.Ident(c.universe.TermName("_root_"))""": Tree
path.split('.').foldLeft(init) { (acc, part) =>
q"c.universe.Select($acc, c.universe.TermName($part))"
}
}
private def liftField(value: Tree, tpe: Tree): Tree = {
q"_root_.scala.Predef.implicitly[c.universe.Liftable[$tpe]].apply($value)"
}
}