Skip to content

Commit 3edd16b

Browse files
committed
Only evaluate transparent inline unapply once
Fixes #19369
1 parent 8039426 commit 3edd16b

File tree

7 files changed

+67
-10
lines changed

7 files changed

+67
-10
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

+32-2
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.*
@@ -1446,7 +1447,36 @@ trait Applications extends Compatibility {
14461447
unapplyArgType
14471448

14481449
val dummyArg = dummyTreeOfType(ownType)
1449-
val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
1450+
val (newUnapplyFn, unapplyApp) =
1451+
val unapplyAppCall = withMode(Mode.NoInline) {
1452+
typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil)))
1453+
}
1454+
/** Inlines the unapply function before the dummy argument
1455+
*
1456+
* A call `P.unapply[...](using l1, ..)(`dummy`)(using t1, ..)` becomes
1457+
* ```
1458+
* {
1459+
* class $anon {
1460+
* def unapply(s: S)(using t1: T1, ..): R =
1461+
* ... // inlined code for: P.unapply[...](using l1, ..)(s)(using t1, ..)
1462+
* }
1463+
* new $anon
1464+
* }.unapply(`dummy`)(using t1, ..)
1465+
* ```
1466+
*/
1467+
def inlinedUnapplyFnAndApp(unapp: Tree): (Tree, Tree) = unapp match {
1468+
case DynamicUnapply(_) =>
1469+
report.error(em"Structural unapply is not supported", unapplyFn.srcPos)
1470+
(unapplyFn, unapplyAppCall)
1471+
case Apply(fn, `dummyArg` :: Nil) =>
1472+
val inlinedUnapplyFn = Inlines.inlinedUnapplyFun(fn)
1473+
(inlinedUnapplyFn, inlinedUnapplyFn.appliedToArgs(`dummyArg` :: Nil))
1474+
case Apply(fn, args) =>
1475+
val (fn1, app) = inlinedUnapplyFnAndApp(fn)
1476+
(fn1, tpd.cpy.Apply(unapp)(app, args))
1477+
}
1478+
if unapplyAppCall.symbol.isAllOf(Transparent | Inline) then inlinedUnapplyFnAndApp(unapplyAppCall)
1479+
else (unapplyFn, unapplyAppCall)
14501480
def unapplyImplicits(unapp: Tree): List[Tree] = {
14511481
val res = List.newBuilder[Tree]
14521482
def loop(unapp: Tree): Unit = unapp match {
@@ -1475,7 +1505,7 @@ trait Applications extends Compatibility {
14751505
List.fill(argTypes.length - args.length)(WildcardType)
14761506
}
14771507
val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _))
1478-
val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType)
1508+
val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType)
14791509
unapp.println(s"unapply patterns = $unapplyPatterns")
14801510
if (ownType.stripped eq selType.stripped) || ownType.isError then result
14811511
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
@@ -1938,7 +1938,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
19381938
if (bounds != null) sym.info = bounds
19391939
}
19401940
b
1941-
case t: UnApply if t.symbol.is(Inline) => Inlines.inlinedUnapply(t)
1941+
case t: UnApply if t.symbol.is(Inline) =>
1942+
assert(!t.symbol.is(Transparent))
1943+
cpy.UnApply(t)(fun = Inlines.inlinedUnapplyFun(t.fun)) // TODO inline these in the inlining phase (see #19382)
19421944
case t => t
19431945
}
19441946
}

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)