Skip to content

Commit 7e78711

Browse files
Backport "Only evaluate transparent inline unapply once" to LTS (#21047)
Backports #19380 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents d229e72 + 60ecf4e commit 7e78711

File tree

7 files changed

+85
-23
lines changed

7 files changed

+85
-23
lines changed

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

+5-7
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,10 @@ object Inlines:
172172
/** Try to inline a pattern with an inline unapply method. Fail with error if the maximal
173173
* inline depth is exceeded.
174174
*
175-
* @param unapp The tree of the pattern to inline
175+
* @param fun The function of an Unapply node
176176
* @return An `Unapply` with a `fun` containing the inlined call to the unapply
177177
*/
178-
def inlinedUnapply(unapp: tpd.UnApply)(using Context): Tree =
178+
def inlinedUnapplyFun(fun: tpd.Tree)(using Context): Tree =
179179
// We cannot inline the unapply directly, since the pattern matcher relies on unapply applications
180180
// as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards.
181181
// So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply
@@ -189,7 +189,6 @@ object Inlines:
189189
// transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing
190190
// the call to the `unapply`.
191191

192-
val fun = unapp.fun
193192
val sym = fun.symbol
194193

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

202-
val unapply = DefDef(unapplySym.asTerm, argss => fun.appliedToArgss(argss).withSpan(unapp.span))
201+
val unapply = DefDef(unapplySym.asTerm, argss => fun.appliedToArgss(argss).withSpan(fun.span))
203202

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

217-
val newFun = newUnapply.select(sym.name).withSpan(unapp.span)
218-
cpy.UnApply(unapp)(fun = newFun)
219-
end inlinedUnapply
216+
newUnapply.select(sym.name).withSpan(fun.span)
217+
end inlinedUnapplyFun
220218

221219
/** For a retained inline method, another method that keeps track of
222220
* the body that is kept at runtime. For instance, an inline method

compiler/src/dotty/tools/dotc/typer/Applications.scala

+50-15
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Denotations.SingleDenotation
3434
import annotation.threadUnsafe
3535

3636
import scala.util.control.NonFatal
37+
import dotty.tools.dotc.inlines.Inlines
3738

3839
object Applications {
3940
import tpd.*
@@ -1392,6 +1393,50 @@ trait Applications extends Compatibility {
13921393
}
13931394
}
13941395

1396+
/** Inlines the unapply function before the dummy argument
1397+
*
1398+
* A call `P.unapply[...](using l1, ..)(`dummy`)(using t1, ..)` becomes
1399+
* ```
1400+
* {
1401+
* class $anon {
1402+
* def unapply(s: S)(using t1: T1, ..): R =
1403+
* ... // inlined code for: P.unapply[...](using l1, ..)(s)(using t1, ..)
1404+
* }
1405+
* new $anon
1406+
* }.unapply(`dummy`)(using t1, ..)
1407+
* ```
1408+
*/
1409+
def inlinedUnapplyFnAndApp(dummyArg: Tree, unapplyAppCall: Tree): (Tree, Tree) =
1410+
def rec(unapp: Tree): (Tree, Tree) =
1411+
unapp match
1412+
case DynamicUnapply(_) =>
1413+
report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1414+
(unapplyFn, unapplyAppCall)
1415+
case Apply(fn, `dummyArg` :: Nil) =>
1416+
val inlinedUnapplyFn = Inlines.inlinedUnapplyFun(fn)
1417+
(inlinedUnapplyFn, inlinedUnapplyFn.appliedToArgs(`dummyArg` :: Nil))
1418+
case Apply(fn, args) =>
1419+
val (fn1, app) = rec(fn)
1420+
(fn1, tpd.cpy.Apply(unapp)(app, args))
1421+
1422+
if unapplyAppCall.symbol.isAllOf(Transparent | Inline) then rec(unapplyAppCall)
1423+
else (unapplyFn, unapplyAppCall)
1424+
end inlinedUnapplyFnAndApp
1425+
1426+
def unapplyImplicits(dummyArg: Tree, unapp: Tree): List[Tree] =
1427+
val res = List.newBuilder[Tree]
1428+
def loop(unapp: Tree): Unit = unapp match
1429+
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2
1430+
case Apply(unapply, `dummyArg` :: Nil) =>
1431+
case Inlined(u, _, _) => loop(u)
1432+
case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1433+
case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args
1434+
case _ => ().assertingErrorsReported
1435+
1436+
loop(unapp)
1437+
res.result()
1438+
end unapplyImplicits
1439+
13951440
/** Add a `Bind` node for each `bound` symbol in a type application `unapp` */
13961441
def addBinders(unapp: Tree, bound: List[Symbol]) = unapp match {
13971442
case TypeApply(fn, args) =>
@@ -1430,20 +1475,10 @@ trait Applications extends Compatibility {
14301475
unapplyArgType
14311476

14321477
val dummyArg = dummyTreeOfType(ownType)
1433-
val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
1434-
def unapplyImplicits(unapp: Tree): List[Tree] = {
1435-
val res = List.newBuilder[Tree]
1436-
def loop(unapp: Tree): Unit = unapp match {
1437-
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2
1438-
case Apply(unapply, `dummyArg` :: Nil) =>
1439-
case Inlined(u, _, _) => loop(u)
1440-
case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1441-
case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args
1442-
case _ => ().assertingErrorsReported
1443-
}
1444-
loop(unapp)
1445-
res.result()
1446-
}
1478+
val (newUnapplyFn, unapplyApp) =
1479+
val unapplyAppCall = withMode(Mode.NoInline):
1480+
typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
1481+
inlinedUnapplyFnAndApp(dummyArg, unapplyAppCall)
14471482

14481483
var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.srcPos)
14491484
for (argType <- argTypes) assert(!isBounds(argType), unapplyApp.tpe.show)
@@ -1459,7 +1494,7 @@ trait Applications extends Compatibility {
14591494
List.fill(argTypes.length - args.length)(WildcardType)
14601495
}
14611496
val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _))
1462-
val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType)
1497+
val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(dummyArg, unapplyApp), unapplyPatterns), ownType)
14631498
unapp.println(s"unapply patterns = $unapplyPatterns")
14641499
if (ownType.stripped eq selType.stripped) || ownType.isError then result
14651500
else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType)

compiler/src/dotty/tools/dotc/typer/Typer.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -1858,7 +1858,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18581858
if (bounds != null) sym.info = bounds
18591859
}
18601860
b
1861-
case t: UnApply if t.symbol.is(Inline) => Inlines.inlinedUnapply(t)
1861+
case t: UnApply if t.symbol.is(Inline) =>
1862+
assert(!t.symbol.is(Transparent))
1863+
cpy.UnApply(t)(fun = Inlines.inlinedUnapplyFun(t.fun)) // TODO inline these in the inlining phase (see #19382)
18621864
case t => t
18631865
}
18641866
}

tests/pos-macros/i19369/Macro_1.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted._
2+
3+
object Unapplier:
4+
transparent inline def unapply(arg: Any): Option[Int] = ${unapplyImpl('arg)}
5+
6+
def unapplyImpl(using Quotes)(argExpr: Expr[Any]): Expr[Option[Int]] =
7+
executionCount += 1
8+
assert(executionCount == 1, "macro should only expand once")
9+
'{Some(1)}
10+
11+
private var executionCount = 0

tests/pos-macros/i19369/Test_2.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@main def main() =
2+
val Unapplier(result) = Some(5)

tests/run/i8530.check

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ MyBoooleanUnapply
33
3
44
(4,5)
55
5
6+
6
7+
7

tests/run/i8530.scala

+12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ object MySeqUnapply:
1313
object MyWhiteboxUnapply:
1414
transparent inline def unapply(x: Int): Option[Any] = Some(x)
1515

16+
object MyWhiteboxUnapply1:
17+
transparent inline def unapply(using DummyImplicit)(x: Int)(using DummyImplicit): Option[Any] = Some(x)
18+
19+
object MyWhiteboxUnapply2:
20+
transparent inline def unapply(using DummyImplicit)(using DummyImplicit)(x: Int)(using DummyImplicit)(using DummyImplicit): Option[Any] = Some(x)
21+
1622

1723
@main def Test =
1824
1 match
@@ -30,4 +36,10 @@ object MyWhiteboxUnapply:
3036
5 match
3137
case MyWhiteboxUnapply(x) => println(x: Int)
3238

39+
6 match
40+
case MyWhiteboxUnapply1(x) => println(x: Int)
41+
42+
7 match
43+
case MyWhiteboxUnapply2(x) => println(x: Int)
44+
3345
end Test

0 commit comments

Comments
 (0)