Skip to content

Commit 83bd356

Browse files
authored
Merge pull request #11911 from dotty-staging/fix-11894
Fixes for transitive inlining
2 parents cadf3bf + c48fb9b commit 83bd356

File tree

6 files changed

+136
-35
lines changed

6 files changed

+136
-35
lines changed

compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ class TreeTypeMap(
4141
val substTo: List[Symbol] = Nil)(using Context) extends tpd.TreeMap {
4242
import tpd._
4343

44+
def copy(
45+
typeMap: Type => Type,
46+
treeMap: tpd.Tree => tpd.Tree,
47+
oldOwners: List[Symbol],
48+
newOwners: List[Symbol],
49+
substFrom: List[Symbol],
50+
substTo: List[Symbol])(using Context): TreeTypeMap =
51+
new TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo)
52+
4453
/** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */
4554
def mapOwner(sym: Symbol): Symbol = sym.subst(oldOwners, newOwners)
4655

@@ -76,8 +85,11 @@ class TreeTypeMap(
7685
updateDecls(prevStats.tail, newStats.tail)
7786
}
7887

79-
/** If true, stop return Inlined(Empty, _, _) nodes unchanged */
80-
def stopAtInlinedArgument: Boolean = false
88+
def transformInlined(tree: tpd.Inlined)(using Context): tpd.Tree =
89+
val Inlined(call, bindings, expanded) = tree
90+
val (tmap1, bindings1) = transformDefs(bindings)
91+
val expanded1 = tmap1.transform(expanded)
92+
cpy.Inlined(tree)(call, bindings1, expanded1)
8193

8294
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = treeMap(tree) match {
8395
case impl @ Template(constr, parents, self, _) =>
@@ -109,13 +121,8 @@ class TreeTypeMap(
109121
val (tmap1, stats1) = transformDefs(stats)
110122
val expr1 = tmap1.transform(expr)
111123
cpy.Block(blk)(stats1, expr1)
112-
case inlined @ Inlined(call, bindings, expanded) =>
113-
if stopAtInlinedArgument && call.isEmpty then
114-
inlined
115-
else
116-
val (tmap1, bindings1) = transformDefs(bindings)
117-
val expanded1 = tmap1.transform(expanded)
118-
cpy.Inlined(inlined)(call, bindings1, expanded1)
124+
case inlined: Inlined =>
125+
transformInlined(inlined)
119126
case cdef @ CaseDef(pat, guard, rhs) =>
120127
val tmap = withMappedSyms(patVars(pat))
121128
val pat1 = tmap.transform(pat)
@@ -171,7 +178,7 @@ class TreeTypeMap(
171178
assert(!to.exists(substFrom contains _))
172179
assert(!from.exists(newOwners contains _))
173180
assert(!to.exists(oldOwners contains _))
174-
new TreeTypeMap(
181+
copy(
175182
typeMap,
176183
treeMap,
177184
from ++ oldOwners,

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

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -723,11 +723,40 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
723723
def inlinedFromOutside(tree: Tree)(span: Span): Tree =
724724
Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span)
725725

726+
// InlinerMap is a TreeTypeMap with special treatment for inlined arguments:
727+
// They are generally left alone (not mapped further, and if they wrap a type
728+
// the type Inlined wrapper gets dropped
729+
class InlinerMap(
730+
typeMap: Type => Type,
731+
treeMap: Tree => Tree,
732+
oldOwners: List[Symbol],
733+
newOwners: List[Symbol],
734+
substFrom: List[Symbol],
735+
substTo: List[Symbol])(using Context)
736+
extends TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo):
737+
738+
override def copy(
739+
typeMap: Type => Type,
740+
treeMap: Tree => Tree,
741+
oldOwners: List[Symbol],
742+
newOwners: List[Symbol],
743+
substFrom: List[Symbol],
744+
substTo: List[Symbol])(using Context) =
745+
new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo)
746+
747+
override def transformInlined(tree: Inlined)(using Context) =
748+
if tree.call.isEmpty then
749+
tree.expansion match
750+
case expansion: TypeTree => expansion
751+
case _ => tree
752+
else super.transformInlined(tree)
753+
end InlinerMap
754+
726755
// A tree type map to prepare the inlined body for typechecked.
727756
// The translation maps references to `this` and parameters to
728757
// corresponding arguments or proxies on the type and term level. It also changes
729758
// the owner from the inlined method to the current owner.
730-
val inliner = new TreeTypeMap(
759+
val inliner = new InlinerMap(
731760
typeMap =
732761
new DeepTypeMap {
733762
def apply(t: Type) = t match {
@@ -763,16 +792,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
763792
val inlinedSingleton = singleton(t).withSpan(argSpan)
764793
inlinedFromOutside(inlinedSingleton)(tree.span)
765794
case Some(t) if tree.isType =>
766-
TypeTree(t).withSpan(argSpan)
795+
inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span)
767796
case _ => tree
768797
}
769798
case tree => tree
770799
},
771800
oldOwners = inlinedMethod :: Nil,
772-
newOwners = ctx.owner :: Nil
773-
)(using inlineCtx) {
774-
override def stopAtInlinedArgument: Boolean = true
775-
}
801+
newOwners = ctx.owner :: Nil,
802+
substFrom = Nil,
803+
substTo = Nil
804+
)(using inlineCtx)
776805

777806
// Apply inliner to `rhsToInline`, split off any implicit bindings from result, and
778807
// make them part of `bindingsBuf`. The expansion is then the tree that remains.
@@ -1012,26 +1041,31 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
10121041
*/
10131042
def betaReduce(tree: Tree)(using Context): Tree = tree match {
10141043
case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) =>
1015-
ddef.tpe.widen match {
1016-
case mt: MethodType if ddef.paramss.head.length == args.length =>
1017-
val bindingsBuf = new mutable.ListBuffer[ValOrDefDef]
1018-
val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) =>
1019-
arg.tpe.dealias match {
1020-
case ref @ TermRef(NoPrefix, _) => ref.symbol
1021-
case _ =>
1022-
paramBindingDef(name, paramtp, arg, bindingsBuf)(
1023-
using ctx.withSource(cl.source)
1024-
).symbol
1044+
// closureDef also returns a result for closures wrapped in Inlined nodes.
1045+
// These need to be preserved.
1046+
def recur(cl: Tree): Tree = cl match
1047+
case Inlined(call, bindings, expr) =>
1048+
cpy.Inlined(cl)(call, bindings, recur(expr))
1049+
case _ => ddef.tpe.widen match
1050+
case mt: MethodType if ddef.paramss.head.length == args.length =>
1051+
val bindingsBuf = new mutable.ListBuffer[ValOrDefDef]
1052+
val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) =>
1053+
arg.tpe.dealias match {
1054+
case ref @ TermRef(NoPrefix, _) => ref.symbol
1055+
case _ =>
1056+
paramBindingDef(name, paramtp, arg, bindingsBuf)(
1057+
using ctx.withSource(cl.source)
1058+
).symbol
1059+
}
10251060
}
1026-
}
1027-
val expander = new TreeTypeMap(
1028-
oldOwners = ddef.symbol :: Nil,
1029-
newOwners = ctx.owner :: Nil,
1030-
substFrom = ddef.paramss.head.map(_.symbol),
1031-
substTo = argSyms)
1032-
Block(bindingsBuf.toList, expander.transform(ddef.rhs))
1033-
case _ => tree
1034-
}
1061+
val expander = new TreeTypeMap(
1062+
oldOwners = ddef.symbol :: Nil,
1063+
newOwners = ctx.owner :: Nil,
1064+
substFrom = ddef.paramss.head.map(_.symbol),
1065+
substTo = argSyms)
1066+
Block(bindingsBuf.toList, expander.transform(ddef.rhs)).withSpan(tree.span)
1067+
case _ => tree
1068+
recur(cl)
10351069
case _ => tree
10361070
}
10371071

tests/pos/i11350.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//case class A[T](action: A[T] ?=> String) // now disallowed
2+
3+
class A1[T](action: A1[T] ?=> String = (_: A1[T]) ?=> "") // works
4+
//case class A2[T](action: A2[?] ?=> String) // now disallowed
5+
//case class A3[T](action: A3[T] => String) // now disallowed

tests/pos/i11894.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class CallbackTo[A](val x: A):
2+
inline def runNow(): A = x
3+
inline def toScalaFn: () => A = () => runNow()
4+
def toOption(): Option[A] =
5+
val y: CallbackTo[Option[A]] = ???
6+
val f: () => Option[A] = y.toScalaFn // error
7+
f()

tests/pos/i11894a.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object CallbackTo {
2+
extension [A](self: CallbackTo[Option[A]]) {
3+
transparent inline def asOption: Option[A] =
4+
self.toScalaFn()
5+
}
6+
}
7+
8+
final class CallbackTo[A] (val x: List[A]) {
9+
10+
transparent inline def runNow(): A =
11+
x.head
12+
13+
transparent inline def toScalaFn: () => A =
14+
() => runNow()
15+
16+
def map[B](f: A => B): CallbackTo[B] =
17+
???
18+
19+
def toOption: Option[A] = {
20+
val x = map[Option[A]](Some(_))
21+
val y = x: CallbackTo[Option[A]] // ok: type is what we expect
22+
y.asOption // error
23+
}
24+
}

tests/pos/i11894b.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object CallbackTo {
2+
extension [A](self: CallbackTo[Option[A]]) {
3+
inline def asOption: Option[A] =
4+
self.toScalaFn()
5+
}
6+
}
7+
8+
final class CallbackTo[A] (val x: List[A]) {
9+
10+
inline def runNow(): A =
11+
x.head
12+
13+
inline def toScalaFn: () => A =
14+
() => runNow()
15+
16+
def map[B](f: A => B): CallbackTo[B] =
17+
???
18+
19+
def toOption: Option[A] = {
20+
val x = map[Option[A]](Some(_))
21+
val y = x: CallbackTo[Option[A]] // ok: type is what we expect
22+
y.asOption // error
23+
}
24+
}

0 commit comments

Comments
 (0)