Skip to content

Commit 6868ebb

Browse files
committed
Fix bridge generation for implicit shortcut methods
1 parent 11787d8 commit 6868ebb

File tree

7 files changed

+84
-31
lines changed

7 files changed

+84
-31
lines changed

compiler/src/dotty/tools/dotc/transform/Bridges.scala

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import ast.untpd
99
import collection.{mutable, immutable}
1010
import TypeErasure._
1111
import ValueClasses.isDerivedValueClass
12+
import ShortcutImplicits._
1213

1314
/** A helper class for generating bridge methods in class `root`. */
14-
class Bridges(root: ClassSymbol)(implicit ctx: Context) {
15+
class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Context) {
1516
import ast.tpd._
1617

1718
assert(ctx.phase == ctx.erasurePhase.next)
@@ -26,7 +27,11 @@ class Bridges(root: ClassSymbol)(implicit ctx: Context) {
2627
* only in classes, never in traits.
2728
*/
2829
override def parents = Array(root.superClass)
29-
override def exclude(sym: Symbol) = !sym.is(MethodOrModule) || super.exclude(sym)
30+
31+
override def exclude(sym: Symbol) =
32+
!sym.is(MethodOrModule) ||
33+
isImplicitShortcut(sym) ||
34+
super.exclude(sym)
3035
}
3136

3237
//val site = root.thisType
@@ -81,8 +86,7 @@ class Bridges(root: ClassSymbol)(implicit ctx: Context) {
8186
owner = root,
8287
flags = (member.flags | Method | Bridge | Artifact) &~
8388
(Accessor | ParamAccessor | CaseAccessor | Deferred | Lazy | Module),
84-
coord = bridgePosFor(member))
85-
.enteredAfter(ctx.erasurePhase.asInstanceOf[DenotTransformer]).asTerm
89+
coord = bridgePosFor(member)).enteredAfter(thisPhase).asTerm
8690

8791
ctx.debuglog(
8892
i"""generating bridge from ${other.showLocated}: ${other.info}
@@ -111,8 +115,19 @@ class Bridges(root: ClassSymbol)(implicit ctx: Context) {
111115
*/
112116
def add(stats: List[untpd.Tree]): List[untpd.Tree] = {
113117
val opc = new BridgesCursor()(preErasureCtx)
118+
val ectx = ctx.withPhase(thisPhase)
114119
while (opc.hasNext) {
115-
if (!opc.overriding.is(Deferred)) addBridgeIfNeeded(opc.overriding, opc.overridden)
120+
if (!opc.overriding.is(Deferred)) {
121+
addBridgeIfNeeded(opc.overriding, opc.overridden)
122+
123+
if (needsImplicitShortcut(opc.overriding)(ectx))
124+
// implicit shortcuts do not show up in the Bridges cursor, since they
125+
// are created only when referenced. Therefore we need to generate a bridge
126+
// for them specifically, if one is needed for the original methods.
127+
addBridgeIfNeeded(
128+
shortcutMethod(opc.overriding, thisPhase)(ectx),
129+
shortcutMethod(opc.overridden, thisPhase)(ectx))
130+
}
116131
opc.next()
117132
}
118133
if (bridges.isEmpty) stats

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class Erasure extends Phase with DenotTransformer {
9191
ref.derivedSingleDenotation(ref.symbol, transformInfo(ref.symbol, ref.symbol.info))
9292
}
9393

94-
val eraser = new Erasure.Typer
94+
val eraser = new Erasure.Typer(this)
9595

9696
def run(implicit ctx: Context): Unit = {
9797
val unit = ctx.compilationUnit
@@ -314,7 +314,7 @@ object Erasure {
314314
}
315315
}
316316

317-
class Typer extends typer.ReTyper with NoChecking {
317+
class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking {
318318
import Boxing._
319319

320320
def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = {
@@ -672,7 +672,7 @@ object Erasure {
672672

673673
override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = {
674674
val stats1 =
675-
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass).add(stats)
675+
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats)
676676
else stats
677677
super.typedStats(stats1, exprOwner).filter(!_.isEmpty)
678678
}

compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,10 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh
7373
override def initContext(ctx: FreshContext) =
7474
DirectMeth = ctx.addLocation[MutableSymbolMap[Symbol]]()
7575

76-
/** If this option is true, we don't specialize symbols that are known to be only
77-
* targets of monomorphic calls.
78-
* The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch
79-
* scenarios inlining and escape analysis can often remove all calling overhead, so we might as
80-
* well not duplicate the code. We need more experience to decide on the best setting of this option.
81-
*/
82-
final val specializeMonoTargets = true
8376

8477
override def prepareForUnit(tree: Tree)(implicit ctx: Context) =
8578
ctx.fresh.updateStore(DirectMeth, newMutableSymbolMap[Symbol])
8679

87-
/** Should `sym` get a ..$direct companion?
88-
* This is the case if `sym` is a method with a non-nullary implicit function type as final result type.
89-
* However if `specializeMonoTargets` is false, we exclude symbols that are known
90-
* to be only targets of monomorphic calls because they are effectively
91-
* final and don't override anything.
92-
*/
93-
private def shouldBeSpecialized(sym: Symbol)(implicit ctx: Context) =
94-
sym.is(Method, butNot = Accessor) &&
95-
defn.isImplicitFunctionType(sym.info.finalResultType) &&
96-
defn.functionArity(sym.info.finalResultType) > 0 &&
97-
!sym.isAnonymousFunction &&
98-
(specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty)
9980

10081
/** The direct method `m$direct` that accompanies the given method `m`.
10182
* Create one if it does not exist already.
@@ -108,7 +89,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh
10889
override def transformSelect(tree: Select)(implicit ctx: Context) =
10990
if (tree.name == nme.apply &&
11091
defn.isImplicitFunctionType(tree.qualifier.tpe.widen) &&
111-
shouldBeSpecialized(tree.qualifier.symbol)) {
92+
needsImplicitShortcut(tree.qualifier.symbol)) {
11293
def directQual(tree: Tree): Tree = tree match {
11394
case Apply(fn, args) => cpy.Apply(tree)(directQual(fn), args)
11495
case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args)
@@ -127,7 +108,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh
127108
/** Transform methods with implicit function type result according to rewrite rule (1) above */
128109
override def transformDefDef(mdef: DefDef)(implicit ctx: Context): Tree = {
129110
val original = mdef.symbol
130-
if (shouldBeSpecialized(original)) {
111+
if (needsImplicitShortcut(original)) {
131112
val direct = directMethod(original)
132113

133114
// Move @tailrec to the direct method
@@ -171,6 +152,27 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh
171152
object ShortcutImplicits {
172153
val name = "shortcutImplicits"
173154

155+
/** If this option is true, we don't specialize symbols that are known to be only
156+
* targets of monomorphic calls.
157+
* The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch
158+
* scenarios inlining and escape analysis can often remove all calling overhead, so we might as
159+
* well not duplicate the code. We need more experience to decide on the best setting of this option.
160+
*/
161+
final val specializeMonoTargets = true
162+
163+
/** Should `sym` get a ..$direct companion?
164+
* This is the case if `sym` is a method with a non-nullary implicit function type as final result type.
165+
* However if `specializeMonoTargets` is false, we exclude symbols that are known
166+
* to be only targets of monomorphic calls because they are effectively
167+
* final and don't override anything.
168+
*/
169+
def needsImplicitShortcut(sym: Symbol)(implicit ctx: Context) =
170+
sym.is(Method, butNot = Accessor) &&
171+
defn.isImplicitFunctionType(sym.info.finalResultType) &&
172+
defn.functionArity(sym.info.finalResultType) > 0 &&
173+
!sym.isAnonymousFunction &&
174+
(specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty)
175+
174176
/** @pre The type's final result type is an implicit function type `implicit Ts => R`.
175177
* @return The type of the `apply` member of `implicit Ts => R`.
176178
*/
@@ -181,8 +183,6 @@ object ShortcutImplicits {
181183
case info => info.member(nme.apply).info
182184
}
183185

184-
def isImplicitShortcut(sym: Symbol)(implicit ctx: Context) = sym.name.is(DirectMethodName)
185-
186186
/** A new `m$direct` method to accompany the given method `m` */
187187
private def newShortcutMethod(sym: Symbol)(implicit ctx: Context): Symbol =
188188
sym.copy(
@@ -195,6 +195,8 @@ object ShortcutImplicits {
195195
sym.owner.info.decl(DirectMethodName(sym.name.asTermName))
196196
.suchThat(_.info matches directInfo(sym.info)).symbol
197197
.orElse(newShortcutMethod(sym).enteredAfter(phase))
198+
199+
def isImplicitShortcut(sym: Symbol)(implicit ctx: Context) = sym.name.is(DirectMethodName)
198200
}
199201

200202

tests/pos/i4753.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class A
2+
3+
trait Foo {
4+
def foo: implicit A => Int
5+
}
6+
7+
class Test {
8+
new FooI{}
9+
}
10+
11+
class FooI extends Foo {
12+
def foo: implicit A => Int = 3
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package implicitShortcut
2+
3+
class C
4+
abstract class Base[T] {
5+
6+
def foo(x: T): implicit C => T = x
7+
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package implicitShortcut
2+
3+
class Derived extends Base[Int] {
4+
override def foo(x: Int): implicit C => Int = 42
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import implicitShortcut._
2+
object Test extends App {
3+
4+
val d = new Derived
5+
val b: Base[Int] = d
6+
implicit val c: C = new C
7+
8+
assert(b.foo(1) == 42)
9+
assert(d.foo(1) == 42)
10+
}

0 commit comments

Comments
 (0)