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

Only evaluate transparent inline unapply once #19380

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
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
12 changes: 5 additions & 7 deletions compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ object Inlines:
/** Try to inline a pattern with an inline unapply method. Fail with error if the maximal
* inline depth is exceeded.
*
* @param unapp The tree of the pattern to inline
* @param fun The function of an Unapply node
* @return An `Unapply` with a `fun` containing the inlined call to the unapply
*/
def inlinedUnapply(unapp: tpd.UnApply)(using Context): Tree =
def inlinedUnapplyFun(fun: tpd.Tree)(using Context): Tree =
// We cannot inline the unapply directly, since the pattern matcher relies on unapply applications
// as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards.
// So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply
Expand All @@ -189,7 +189,6 @@ object Inlines:
// transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing
// the call to the `unapply`.

val fun = unapp.fun
val sym = fun.symbol

val newUnapply = AnonClass(ctx.owner, List(defn.ObjectType), sym.coord) { cls =>
Expand All @@ -199,7 +198,7 @@ object Inlines:
val unapplyInfo = fun.tpe.widen
val unapplySym = newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered

val unapply = DefDef(unapplySym.asTerm, argss => fun.appliedToArgss(argss).withSpan(unapp.span))
val unapply = DefDef(unapplySym.asTerm, argss => fun.appliedToArgss(argss).withSpan(fun.span))

if sym.is(Transparent) then
// Inline the body and refine the type of the unapply method
Expand All @@ -214,9 +213,8 @@ object Inlines:
List(unapply)
}

val newFun = newUnapply.select(sym.name).withSpan(unapp.span)
cpy.UnApply(unapp)(fun = newFun)
end inlinedUnapply
newUnapply.select(sym.name).withSpan(fun.span)
end inlinedUnapplyFun

/** For a retained inline method, another method that keeps track of
* the body that is kept at runtime. For instance, an inline method
Expand Down
65 changes: 50 additions & 15 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import Denotations.SingleDenotation
import annotation.threadUnsafe

import scala.util.control.NonFatal
import dotty.tools.dotc.inlines.Inlines

object Applications {
import tpd.*
Expand Down Expand Up @@ -1408,6 +1409,50 @@ trait Applications extends Compatibility {
}
}

/** Inlines the unapply function before the dummy argument
*
* A call `P.unapply[...](using l1, ..)(`dummy`)(using t1, ..)` becomes
* ```
* {
* class $anon {
* def unapply(s: S)(using t1: T1, ..): R =
* ... // inlined code for: P.unapply[...](using l1, ..)(s)(using t1, ..)
* }
* new $anon
* }.unapply(`dummy`)(using t1, ..)
* ```
*/
def inlinedUnapplyFnAndApp(dummyArg: Tree, unapplyAppCall: Tree): (Tree, Tree) =
def rec(unapp: Tree): (Tree, Tree) =
unapp match
case DynamicUnapply(_) =>
report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
(unapplyFn, unapplyAppCall)
case Apply(fn, `dummyArg` :: Nil) =>
val inlinedUnapplyFn = Inlines.inlinedUnapplyFun(fn)
(inlinedUnapplyFn, inlinedUnapplyFn.appliedToArgs(`dummyArg` :: Nil))
case Apply(fn, args) =>
val (fn1, app) = rec(fn)
(fn1, tpd.cpy.Apply(unapp)(app, args))

if unapplyAppCall.symbol.isAllOf(Transparent | Inline) then rec(unapplyAppCall)
else (unapplyFn, unapplyAppCall)
end inlinedUnapplyFnAndApp

def unapplyImplicits(dummyArg: Tree, unapp: Tree): List[Tree] =
val res = List.newBuilder[Tree]
def loop(unapp: Tree): Unit = unapp match
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2
case Apply(unapply, `dummyArg` :: Nil) =>
case Inlined(u, _, _) => loop(u)
case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args
case _ => ().assertingErrorsReported

loop(unapp)
res.result()
end unapplyImplicits

/** Add a `Bind` node for each `bound` symbol in a type application `unapp` */
def addBinders(unapp: Tree, bound: List[Symbol]) = unapp match {
case TypeApply(fn, args) =>
Expand Down Expand Up @@ -1446,20 +1491,10 @@ trait Applications extends Compatibility {
unapplyArgType

val dummyArg = dummyTreeOfType(ownType)
val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
def unapplyImplicits(unapp: Tree): List[Tree] = {
val res = List.newBuilder[Tree]
def loop(unapp: Tree): Unit = unapp match {
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2
case Apply(unapply, `dummyArg` :: Nil) =>
case Inlined(u, _, _) => loop(u)
case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args
case _ => ().assertingErrorsReported
}
loop(unapp)
res.result()
}
val (newUnapplyFn, unapplyApp) =
val unapplyAppCall = withMode(Mode.NoInline):
typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer fewerBraces style instead, with a :. Did you consider that?

inlinedUnapplyFnAndApp(dummyArg, unapplyAppCall)

var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.srcPos)
for (argType <- argTypes) assert(!isBounds(argType), unapplyApp.tpe.show)
Expand All @@ -1475,7 +1510,7 @@ trait Applications extends Compatibility {
List.fill(argTypes.length - args.length)(WildcardType)
}
val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _))
val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType)
val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(dummyArg, unapplyApp), unapplyPatterns), ownType)
unapp.println(s"unapply patterns = $unapplyPatterns")
if (ownType.stripped eq selType.stripped) || ownType.isError then result
else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType)
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1938,7 +1938,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
if (bounds != null) sym.info = bounds
}
b
case t: UnApply if t.symbol.is(Inline) => Inlines.inlinedUnapply(t)
case t: UnApply if t.symbol.is(Inline) =>
assert(!t.symbol.is(Transparent))
cpy.UnApply(t)(fun = Inlines.inlinedUnapplyFun(t.fun)) // TODO inline these in the inlining phase (see #19382)
case t => t
}
}
Expand Down
11 changes: 11 additions & 0 deletions tests/pos-macros/i19369/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import scala.quoted._

object Unapplier:
transparent inline def unapply(arg: Any): Option[Int] = ${unapplyImpl('arg)}

def unapplyImpl(using Quotes)(argExpr: Expr[Any]): Expr[Option[Int]] =
executionCount += 1
assert(executionCount == 1, "macro should only expand once")
'{Some(1)}

private var executionCount = 0
2 changes: 2 additions & 0 deletions tests/pos-macros/i19369/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@main def main() =
val Unapplier(result) = Some(5)
2 changes: 2 additions & 0 deletions tests/run/i8530.check
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ MyBoooleanUnapply
3
(4,5)
5
6
7
12 changes: 12 additions & 0 deletions tests/run/i8530.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ object MySeqUnapply:
object MyWhiteboxUnapply:
transparent inline def unapply(x: Int): Option[Any] = Some(x)

object MyWhiteboxUnapply1:
transparent inline def unapply(using DummyImplicit)(x: Int)(using DummyImplicit): Option[Any] = Some(x)

object MyWhiteboxUnapply2:
transparent inline def unapply(using DummyImplicit)(using DummyImplicit)(x: Int)(using DummyImplicit)(using DummyImplicit): Option[Any] = Some(x)


@main def Test =
1 match
Expand All @@ -30,4 +36,10 @@ object MyWhiteboxUnapply:
5 match
case MyWhiteboxUnapply(x) => println(x: Int)

6 match
case MyWhiteboxUnapply1(x) => println(x: Int)

7 match
case MyWhiteboxUnapply2(x) => println(x: Int)

end Test
Loading