Skip to content

Commit

Permalink
Only evaluate transparent inline unapply once
Browse files Browse the repository at this point in the history
Fixes #19369
  • Loading branch information
nicolasstucki committed Jan 18, 2024
1 parent 8039426 commit 5648f12
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 10 deletions.
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
39 changes: 37 additions & 2 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,36 @@ 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

/** 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,7 +1477,11 @@ trait Applications extends Compatibility {
unapplyArgType

val dummyArg = dummyTreeOfType(ownType)
val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
val (newUnapplyFn, unapplyApp) =
val unapplyAppCall = withMode(Mode.NoInline):
typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
inlinedUnapplyFnAndApp(dummyArg, unapplyAppCall)

def unapplyImplicits(unapp: Tree): List[Tree] = {
val res = List.newBuilder[Tree]
def loop(unapp: Tree): Unit = unapp match {
Expand Down Expand Up @@ -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(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

0 comments on commit 5648f12

Please sign in to comment.