Skip to content

Commit 92fe2a5

Browse files
authored
Merge pull request #2342 from dotty-staging/bridge
New bridge implementation
2 parents 13443fa + e4d530b commit 92fe2a5

14 files changed

+242
-132
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import core._
6+
import Symbols._, Types._, Contexts._, Decorators._, SymDenotations._, Flags._, Scopes._
7+
import DenotTransformers._
8+
import ast.untpd
9+
import collection.{mutable, immutable}
10+
import TypeErasure._
11+
import ValueClasses.isDerivedValueClass
12+
13+
/** A helper class for generating bridge methods in class `root`. */
14+
class Bridges(root: ClassSymbol)(implicit ctx: Context) {
15+
import ast.tpd._
16+
17+
assert(ctx.phase == ctx.erasurePhase.next)
18+
private val preErasureCtx = ctx.withPhase(ctx.erasurePhase)
19+
20+
private class BridgesCursor(implicit ctx: Context) extends OverridingPairs.Cursor(root) {
21+
22+
/** Only use the superclass of `root` as a parent class. This means
23+
* overriding pairs that have a common implementation in a trait parent
24+
* are also counted. This is necessary because we generate bridge methods
25+
* only in classes, never in traits.
26+
*/
27+
override def parents = Array(root.superClass)
28+
override def exclude(sym: Symbol) = !sym.is(Method) || super.exclude(sym)
29+
}
30+
31+
//val site = root.thisType
32+
33+
private var toBeRemoved = immutable.Set[Symbol]()
34+
private val bridges = mutable.ListBuffer[Tree]()
35+
private val bridgesScope = newScope
36+
private val bridgeTarget = mutable.HashMap[Symbol, Symbol]()
37+
38+
/** Add a bridge between `member` and `other`, where `member` overrides `other`
39+
* before erasure, if the following conditions are satisfied.
40+
*
41+
* - `member` and other have different signatures
42+
* - `member` is not inline
43+
* - there is not yet a bridge with the same name and signature in `root`
44+
*
45+
* The bridge has the erased info of `other` and forwards to `member`.
46+
*/
47+
private def addBridgeIfNeeded(member: Symbol, other: Symbol) = {
48+
def bridgeExists =
49+
bridgesScope.lookupAll(member.name).exists(bridge =>
50+
bridgeTarget(bridge) == member && bridge.signature == other.signature)
51+
if (!(member.is(Inline) || member.signature == other.signature || bridgeExists))
52+
addBridge(member, other)
53+
}
54+
55+
/** Generate bridge between `member` and `other`
56+
*/
57+
private def addBridge(member: Symbol, other: Symbol) = {
58+
val bridgePos = if (member.owner == root && member.pos.exists) member.pos else root.pos
59+
val bridge = other.copy(
60+
owner = root,
61+
flags = (member.flags | Method | Bridge | Artifact) &~
62+
(Accessor | ParamAccessor | CaseAccessor | Deferred | Lazy | Module),
63+
coord = bridgePos).enteredAfter(ctx.erasurePhase.asInstanceOf[DenotTransformer]).asTerm
64+
65+
ctx.debuglog(
66+
i"""generating bridge from ${other.showLocated}: ${other.info}
67+
|to ${member.showLocated}: ${member.info} @ ${member.pos}
68+
|bridge: ${bridge.showLocated} with flags: ${bridge.flags}""")
69+
70+
bridgeTarget(bridge) = member
71+
bridgesScope.enter(bridge)
72+
73+
if (other.owner == root) {
74+
root.delete(other)
75+
toBeRemoved += other
76+
}
77+
78+
bridges +=
79+
DefDef(bridge, This(root).select(member).appliedToArgss(_)).withPos(bridge.pos)
80+
}
81+
82+
/** Add all necessary bridges to template statements `stats`, and remove at the same
83+
* time deferred methods in `stats` that are replaced by a bridge with the same signature.
84+
*/
85+
def add(stats: List[untpd.Tree]): List[untpd.Tree] =
86+
if (root.is(Trait)) stats
87+
else {
88+
val opc = new BridgesCursor()(preErasureCtx)
89+
while (opc.hasNext) {
90+
if (!opc.overriding.is(Deferred)) addBridgeIfNeeded(opc.overriding, opc.overridden)
91+
opc.next()
92+
}
93+
if (bridges.isEmpty) stats
94+
else stats.filterNot(stat => toBeRemoved contains stat.symbol) ::: bridges.toList
95+
}
96+
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import TypeErasure.ErasedValueType, ValueClasses._
1111
/** This phase erases ErasedValueType to their underlying type.
1212
* It also removes the synthetic cast methods u2evt$ and evt2u$ which are
1313
* no longer needed afterwards.
14+
* Finally, it checks that we don't introduce "double definitions" of pairs
15+
* of methods that now have the same signature but were not considered matching
16+
* before erasure.
1417
*/
1518
class ElimErasedValueType extends MiniPhaseTransform with InfoTransformer {
1619

@@ -67,6 +70,44 @@ class ElimErasedValueType extends MiniPhaseTransform with InfoTransformer {
6770
transformTypeOfTree(t)
6871
}
6972

73+
/** Check that we don't have pairs of methods that override each other after
74+
* this phase, yet do not have matching types before erasure.
75+
* The before erasure test is performed after phase elimRepeated, so we
76+
* do not need to special case pairs of `T* / Seq[T]` parameters.
77+
*/
78+
private def checkNoClashes(root: Symbol)(implicit ctx: Context) = {
79+
val opc = new OverridingPairs.Cursor(root) {
80+
override def exclude(sym: Symbol) =
81+
!sym.is(Method) || sym.is(Bridge) || super.exclude(sym)
82+
override def matches(sym1: Symbol, sym2: Symbol) =
83+
sym1.signature == sym2.signature
84+
}
85+
def checkNoConflict(sym1: Symbol, sym2: Symbol, info: Type)(implicit ctx: Context): Unit = {
86+
val site = root.thisType
87+
val info1 = site.memberInfo(sym1)
88+
val info2 = site.memberInfo(sym2)
89+
if (!info1.matchesLoosely(info2))
90+
ctx.error(
91+
em"""double definition:
92+
|$sym1: $info1 in ${sym1.owner} ${sym1.flags} and
93+
|$sym2: $info2 in ${sym2.owner} ${sym2.flags}
94+
|have same type after erasure: $info""",
95+
root.pos)
96+
}
97+
val earlyCtx = ctx.withPhase(ctx.elimRepeatedPhase.next)
98+
while (opc.hasNext) {
99+
val sym1 = opc.overriding
100+
val sym2 = opc.overridden
101+
checkNoConflict(sym1, sym2, sym1.info)(earlyCtx)
102+
opc.next()
103+
}
104+
}
105+
106+
override def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
107+
checkNoClashes(tree.symbol)
108+
tree
109+
}
110+
70111
override def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree =
71112
transformTypeOfTree(tree)
72113

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

Lines changed: 8 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Erasure extends Phase with DenotTransformer { thisTransformer =>
6767
val oldOwner = ref.owner
6868
val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner
6969
val oldInfo = ref.info
70-
val newInfo = transformInfo(ref.symbol, oldInfo)
70+
val newInfo = transformInfo(oldSymbol, oldInfo)
7171
val oldFlags = ref.flags
7272
val newFlags =
7373
if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner)) oldFlags &~ Flags.Param
@@ -594,117 +594,10 @@ object Erasure extends TypeTestsCasts{
594594
EmptyTree
595595

596596
override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = {
597-
val stats1 = Trees.flatten(super.typedStats(stats, exprOwner))
598-
if (ctx.owner.isClass) stats1 ::: addBridges(stats, stats1)(ctx) else stats1
599-
}
600-
601-
// this implementation doesn't check for bridge clashes with value types!
602-
def addBridges(oldStats: List[untpd.Tree], newStats: List[tpd.Tree])(implicit ctx: Context): List[tpd.Tree] = {
603-
val beforeCtx = ctx.withPhase(ctx.erasurePhase)
604-
def traverse(after: List[Tree], before: List[untpd.Tree],
605-
emittedBridges: ListBuffer[tpd.DefDef] = ListBuffer[tpd.DefDef]()): List[tpd.DefDef] = {
606-
after match {
607-
case Nil => emittedBridges.toList
608-
case (member: DefDef) :: newTail =>
609-
before match {
610-
case Nil => emittedBridges.toList
611-
case (oldMember: untpd.DefDef) :: oldTail =>
612-
try {
613-
val oldSymbol = oldMember.symbol(beforeCtx)
614-
val newSymbol = member.symbol(ctx)
615-
assert(oldSymbol.name(beforeCtx) == newSymbol.name,
616-
s"${oldSymbol.name(beforeCtx)} bridging with ${newSymbol.name}")
617-
val newOverridden = oldSymbol.denot.allOverriddenSymbols.toSet // TODO: clarify new <-> old in a comment; symbols are swapped here
618-
val oldOverridden = newSymbol.allOverriddenSymbols(beforeCtx).toSet // TODO: can we find a more efficient impl? newOverridden does not have to be a set!
619-
def stillInBaseClass(sym: Symbol) = ctx.owner derivesFrom sym.owner
620-
val neededBridges = (oldOverridden -- newOverridden).filter(stillInBaseClass)
621-
622-
var minimalSet = Set[Symbol]()
623-
// compute minimal set of bridges that are needed:
624-
for (bridge <- neededBridges) {
625-
val isRequired = minimalSet.forall(nxtBridge => !(bridge.info =:= nxtBridge.info))
626-
627-
if (isRequired) {
628-
// check for clashes
629-
val clash: Option[Symbol] = oldSymbol.owner.info.decls.lookupAll(bridge.name).find {
630-
sym =>
631-
(sym.name eq bridge.name) && sym.info.widen =:= bridge.info.widen
632-
}.orElse(
633-
emittedBridges.find(stat => (stat.name == bridge.name) && stat.tpe.widen =:= bridge.info.widen)
634-
.map(_.symbol))
635-
clash match {
636-
case Some(cl) =>
637-
ctx.error(i"bridge for method ${newSymbol.showLocated(beforeCtx)} of type ${newSymbol.info(beforeCtx)}\n" +
638-
i"clashes with ${cl.symbol.showLocated(beforeCtx)} of type ${cl.symbol.info(beforeCtx)}\n" +
639-
i"both have same type after erasure: ${bridge.symbol.info}")
640-
case None => minimalSet += bridge
641-
}
642-
}
643-
}
644-
645-
val bridgeImplementations = minimalSet.map {
646-
sym => makeBridgeDef(member, sym)(ctx)
647-
}
648-
emittedBridges ++= bridgeImplementations
649-
} catch {
650-
case ex: MergeError => ctx.error(ex.getMessage, member.pos)
651-
}
652-
653-
traverse(newTail, oldTail, emittedBridges)
654-
case notADefDef :: oldTail =>
655-
traverse(after, oldTail, emittedBridges)
656-
}
657-
case notADefDef :: newTail =>
658-
traverse(newTail, before, emittedBridges)
659-
}
660-
}
661-
662-
traverse(newStats, oldStats)
663-
}
664-
665-
private final val NoBridgeFlags = Flags.Accessor | Flags.Deferred | Flags.Lazy | Flags.ParamAccessor
666-
667-
/** Create a bridge DefDef which overrides a parent method.
668-
*
669-
* @param newDef The DefDef which needs bridging because its signature
670-
* does not match the parent method signature
671-
* @param parentSym A symbol corresponding to the parent method to override
672-
* @return A new DefDef whose signature matches the parent method
673-
* and whose body only contains a call to newDef
674-
*/
675-
def makeBridgeDef(newDef: tpd.DefDef, parentSym: Symbol)(implicit ctx: Context): tpd.DefDef = {
676-
val newDefSym = newDef.symbol
677-
val currentClass = newDefSym.owner.asClass
678-
679-
def error(reason: String) = {
680-
assert(false, s"failure creating bridge from ${newDefSym} to ${parentSym}, reason: $reason")
681-
???
682-
}
683-
var excluded = NoBridgeFlags
684-
if (!newDefSym.is(Flags.Protected)) excluded |= Flags.Protected // needed to avoid "weaker access" assertion failures in expandPrivate
685-
val bridge = ctx.newSymbol(currentClass,
686-
parentSym.name, parentSym.flags &~ excluded | Flags.Bridge, parentSym.info, coord = newDefSym.owner.coord).asTerm
687-
bridge.enteredAfter(ctx.phase.prev.asInstanceOf[DenotTransformer]) // this should be safe, as we're executing in context of next phase
688-
ctx.debuglog(s"generating bridge from ${newDefSym} to $bridge")
689-
690-
val sel: Tree = This(currentClass).select(newDefSym.termRef)
691-
692-
val resultType = parentSym.info.widen.resultType
693-
694-
val bridgeCtx = ctx.withOwner(bridge)
695-
696-
tpd.DefDef(bridge, { paramss: List[List[tpd.Tree]] =>
697-
implicit val ctx = bridgeCtx
698-
699-
val rhs = paramss.foldLeft(sel)((fun, vparams) =>
700-
fun.tpe.widen match {
701-
case mt: MethodType =>
702-
Apply(fun, (vparams, mt.paramInfos).zipped.map(adapt(_, _, untpd.EmptyTree)))
703-
case a =>
704-
error(s"can not resolve apply type $a")
705-
})
706-
adapt(rhs, resultType)
707-
})
597+
val stats1 =
598+
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass).add(stats)
599+
else stats
600+
super.typedStats(stats1, exprOwner)
708601
}
709602

710603
override def adapt(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree =
@@ -715,4 +608,7 @@ object Erasure extends TypeTestsCasts{
715608
else adaptToType(tree, pt)
716609
}
717610
}
611+
612+
def takesBridges(sym: Symbol)(implicit ctx: Context) =
613+
sym.isClass && !sym.is(Flags.Trait | Flags.Package)
718614
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ object OverridingPairs {
3131
*/
3232
protected def exclude(sym: Symbol): Boolean = !sym.memberCanMatchInheritedSymbols
3333

34-
/** The parents of base (may also be refined).
34+
/** The parents of base that are checked when deciding whether an overriding
35+
* pair has already been treated in a parent class.
36+
* This may be refined in subclasses. @see Bridges for a use case.
3537
*/
3638
protected def parents: Array[Symbol] = base.info.parents.toArray map (_.typeSymbol)
3739

compiler/test/dotc/tests.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ class tests extends CompilerTest {
193193
@Test def neg_i1050 = compileFile(negCustomArgs, "i1050", List("-strict"))
194194
@Test def neg_i1240 = compileFile(negCustomArgs, "i1240")(allowDoubleBindings)
195195
@Test def neg_i2002 = compileFile(negCustomArgs, "i2002")(allowDoubleBindings)
196+
@Test def neg_valueclasses_doubledefs = compileFile(negCustomArgs, "valueclasses-doubledefs")(allowDoubleBindings)
197+
@Test def neg_valueclasses_doubledefs2 = compileFile(negCustomArgs, "valueclasses-doubledefs2")(allowDoubleBindings)
198+
@Test def neg_valueclasses_pavlov = compileFile(negCustomArgs, "valueclasses-pavlov")(allowDoubleBindings)
196199

197200
val negTailcallDir = negDir + "tailcall/"
198201
@Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b")

tests/untried/neg/valueclasses-doubledefs.scala renamed to tests/neg/customArgs/valueclasses-doubledefs.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ class Meter(val x: Double) extends AnyVal
22

33
class Foo {
44
def apply(x: Double) = x.toString
5-
def apply(x: Meter) = x.toString
5+
def apply(x: Meter) = x.toString // error: double def
66
}
7+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Meter(val x: Double) extends AnyVal
2+
3+
trait A {
4+
def apply(x: Double) = x.toString
5+
}
6+
trait B {
7+
def apply(x: Meter) = x.toString
8+
}
9+
10+
object Test extends A with B // error: double def

tests/untried/neg/valueclasses-pavlov.scala renamed to tests/neg/customArgs/valueclasses-pavlov.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ trait Foo[T <: AnyVal] extends Any {
55

66
class Box1(val value: String) extends AnyVal with Foo[Box2] {
77
def foo(x: String) = "foo(String): ok"
8-
def foo(x: Box2) = "foo(Box2): ok"
8+
def foo(x: Box2) = "foo(Box2): ok" // error: double def
99
}
1010

1111
class Box2(val value: String) extends AnyVal
@@ -17,7 +17,7 @@ object test2a {
1717
val b1 = new Box1(null)
1818
val b2 = new Box2(null)
1919
val f: Foo[Box2] = b1
20-
println(f.foo(""))
21-
println(f.foo(b2))
20+
println(f.foo("")) // error: cannot merge
21+
println(f.foo(b2)) // error: cannot merge
2222
}
2323
}

tests/neg/i1240a.scala renamed to tests/pending/neg/i1240a.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class B extends A {
1212
override def give[X] = Nil
1313
override def foo[B](x: C[B]): C[B] = {println("B.C"); x} // error: merge error during erasure
1414
val a: A = this
15-
a.foo(a.give[Int]) // what method should be called here in runtime?
15+
}
16+
17+
object Test extends B {
18+
def main(args: Array[String]): Unit =
19+
a.foo(a.give[Int]) // what method should be called here in runtime?
1620
}
1721

File renamed without changes.

tests/run/i2337.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Minimized from collection strawman
3+
This issue has a lot to do with both mixin and bridge generation and subtleties in JVM spec
4+
if something breaks in this test, this is not a minor issue. Be careful. Here be dragons.
5+
*/
6+
7+
trait Define[A] {
8+
protected def coll: Define[A]
9+
def s = coll
10+
}
11+
12+
trait Iterable[A] extends Define[A] {
13+
protected def coll: this.type = this
14+
}
15+
16+
trait Seq[A] extends Iterable[A]
17+
18+
trait Super1[A] {
19+
protected def coll: Iterable[A]
20+
}
21+
22+
trait Super2[A] extends Super1[A] {
23+
override protected def coll: Seq[A]
24+
}
25+
26+
class Foo[T] extends Seq[T] with Super2[T] {
27+
}
28+
29+
object Test {
30+
def main(args: Array[String]): Unit = {
31+
val foo = new Foo[Int]
32+
foo.s
33+
}
34+
}

0 commit comments

Comments
 (0)