Skip to content

Commit 997103d

Browse files
authored
Only evaluate transparent inline unapply once (#19380)
Fixes #19369
2 parents 71b78e6 + 535f230 commit 997103d

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.*
@@ -1408,6 +1409,50 @@ trait Applications extends Compatibility {
14081409
}
14091410
}
14101411

1412+
/** Inlines the unapply function before the dummy argument
1413+
*
1414+
* A call `P.unapply[...](using l1, ..)(`dummy`)(using t1, ..)` becomes
1415+
* ```
1416+
* {
1417+
* class $anon {
1418+
* def unapply(s: S)(using t1: T1, ..): R =
1419+
* ... // inlined code for: P.unapply[...](using l1, ..)(s)(using t1, ..)
1420+
* }
1421+
* new $anon
1422+
* }.unapply(`dummy`)(using t1, ..)
1423+
* ```
1424+
*/
1425+
def inlinedUnapplyFnAndApp(dummyArg: Tree, unapplyAppCall: Tree): (Tree, Tree) =
1426+
def rec(unapp: Tree): (Tree, Tree) =
1427+
unapp match
1428+
case DynamicUnapply(_) =>
1429+
report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1430+
(unapplyFn, unapplyAppCall)
1431+
case Apply(fn, `dummyArg` :: Nil) =>
1432+
val inlinedUnapplyFn = Inlines.inlinedUnapplyFun(fn)
1433+
(inlinedUnapplyFn, inlinedUnapplyFn.appliedToArgs(`dummyArg` :: Nil))
1434+
case Apply(fn, args) =>
1435+
val (fn1, app) = rec(fn)
1436+
(fn1, tpd.cpy.Apply(unapp)(app, args))
1437+
1438+
if unapplyAppCall.symbol.isAllOf(Transparent | Inline) then rec(unapplyAppCall)
1439+
else (unapplyFn, unapplyAppCall)
1440+
end inlinedUnapplyFnAndApp
1441+
1442+
def unapplyImplicits(dummyArg: Tree, unapp: Tree): List[Tree] =
1443+
val res = List.newBuilder[Tree]
1444+
def loop(unapp: Tree): Unit = unapp match
1445+
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2
1446+
case Apply(unapply, `dummyArg` :: Nil) =>
1447+
case Inlined(u, _, _) => loop(u)
1448+
case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1449+
case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args
1450+
case _ => ().assertingErrorsReported
1451+
1452+
loop(unapp)
1453+
res.result()
1454+
end unapplyImplicits
1455+
14111456
/** Add a `Bind` node for each `bound` symbol in a type application `unapp` */
14121457
def addBinders(unapp: Tree, bound: List[Symbol]) = unapp match {
14131458
case TypeApply(fn, args) =>
@@ -1446,20 +1491,10 @@ trait Applications extends Compatibility {
14461491
unapplyArgType
14471492

14481493
val dummyArg = dummyTreeOfType(ownType)
1449-
val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
1450-
def unapplyImplicits(unapp: Tree): List[Tree] = {
1451-
val res = List.newBuilder[Tree]
1452-
def loop(unapp: Tree): Unit = unapp match {
1453-
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2
1454-
case Apply(unapply, `dummyArg` :: Nil) =>
1455-
case Inlined(u, _, _) => loop(u)
1456-
case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1457-
case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args
1458-
case _ => ().assertingErrorsReported
1459-
}
1460-
loop(unapp)
1461-
res.result()
1462-
}
1494+
val (newUnapplyFn, unapplyApp) =
1495+
val unapplyAppCall = withMode(Mode.NoInline):
1496+
typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
1497+
inlinedUnapplyFnAndApp(dummyArg, unapplyAppCall)
14631498

14641499
var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.srcPos)
14651500
for (argType <- argTypes) assert(!isBounds(argType), unapplyApp.tpe.show)
@@ -1475,7 +1510,7 @@ trait Applications extends Compatibility {
14751510
List.fill(argTypes.length - args.length)(WildcardType)
14761511
}
14771512
val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _))
1478-
val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType)
1513+
val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(dummyArg, unapplyApp), unapplyPatterns), ownType)
14791514
unapp.println(s"unapply patterns = $unapplyPatterns")
14801515
if (ownType.stripped eq selType.stripped) || ownType.isError then result
14811516
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
@@ -1937,7 +1937,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
19371937
if (bounds != null) sym.info = bounds
19381938
}
19391939
b
1940-
case t: UnApply if t.symbol.is(Inline) => Inlines.inlinedUnapply(t)
1940+
case t: UnApply if t.symbol.is(Inline) =>
1941+
assert(!t.symbol.is(Transparent))
1942+
cpy.UnApply(t)(fun = Inlines.inlinedUnapplyFun(t.fun)) // TODO inline these in the inlining phase (see #19382)
19411943
case t => t
19421944
}
19431945
}

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)