Skip to content

Commit bd80a18

Browse files
committed
Harden outer proxy computation of inlined code
It turns out that we simply cannot do reliable outer path computation that fills in the right hand sides of this-proxies from the types of these proxies. As-seen-from logic can mangle the types of proxies enough to scramble the necessary information. What we now do instead is simply count: We record the number of outer accesses to an outer this in inlineable code, and do the same number of outer accesses when computing the proxy.
1 parent c54e942 commit bd80a18

File tree

8 files changed

+60
-30
lines changed

8 files changed

+60
-30
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
342342
private def followOuterLinks(t: Tree)(implicit ctx: Context) = t match {
343343
case t: This if ctx.erasedTypes && !(t.symbol == ctx.owner.enclosingClass || t.symbol.isStaticOwner) =>
344344
// after erasure outer paths should be respected
345-
new ExplicitOuter.OuterOps(ctx).path(t.tpe.widen.classSymbol)
345+
new ExplicitOuter.OuterOps(ctx).path(toCls = t.tpe.widen.classSymbol)
346346
case t =>
347347
t
348348
}

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ object NameOps {
271271
else -1
272272
}
273273

274+
275+
/** The number of hops specified in an outer-select name */
276+
def outerSelectHops: Int = {
277+
require(isOuterSelect)
278+
name.dropRight(nme.OUTER_SELECT.length).toString.toInt
279+
}
280+
274281
/** The name of the generic runtime operation corresponding to an array operation */
275282
def genericArrayOp: TermName = name match {
276283
case nme.apply => nme.array_apply

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ object Erasure extends TypeTestsCasts{
415415
if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
416416
else {
417417
ctx.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}")
418-
outer.path(tree.symbol)
418+
outer.path(toCls = tree.symbol)
419419
}
420420

421421
private def runtimeCallWithProtoArgs(name: Name, pt: Type, args: Tree*)(implicit ctx: Context): Tree = {

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
6060
/** Convert a selection of the form `qual.C_<OUTER>` to an outer path from `qual` to `C` */
6161
override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) =
6262
if (tree.name.isOuterSelect)
63-
outer.path(tree.tpe.widen.classSymbol, tree.qualifier, outOfContext = true).ensureConforms(tree.tpe)
63+
outer.path(start = tree.qualifier, count = tree.name.outerSelectHops)
64+
.ensureConforms(tree.tpe)
6465
else tree
6566

6667
/** First, add outer accessors if a class does not have them yet and it references an outer this.
@@ -354,31 +355,32 @@ object ExplicitOuter {
354355
} else Nil
355356
}
356357

357-
/** The path of outer accessors that references `toCls.this` starting from
358-
* node `start`, which defaults to the context owner's this node.
359-
* @param outOfContext When true, we take the `path` in code that has been inlined
360-
* from somewhere else. In that case, we need to stop not
361-
* just when `toCls` is reached exactly, but also in any superclass
362-
* of `treeCls`. This compensates the `asSeenFrom` logic
363-
* used to compute this-proxies in Inliner.
358+
/** A path of outer accessors starting from node `start`. `start` defaults to the
359+
* context owner's this node. There are two alternative conditions that determine
360+
* where the path ends:
361+
*
362+
* - if the initial `count` parameter is non-negative: where the number of
363+
* outer accessors reaches count.
364+
* - if the initial `count` parameter is negative: where the class symbol of
365+
* the type of the reached tree matches `toCls`.
364366
*/
365-
def path(toCls: Symbol,
366-
start: Tree = This(ctx.owner.lexicallyEnclosingClass.asClass),
367-
outOfContext: Boolean = false): Tree = try {
368-
def loop(tree: Tree): Tree = {
367+
def path(start: Tree = This(ctx.owner.lexicallyEnclosingClass.asClass),
368+
toCls: Symbol = NoSymbol,
369+
count: Int = -1): Tree = try {
370+
def loop(tree: Tree, count: Int): Tree = {
369371
val treeCls = tree.tpe.widen.classSymbol
370372
val outerAccessorCtx = ctx.withPhaseNoLater(ctx.lambdaLiftPhase) // lambdalift mangles local class names, which means we cannot reliably find outer acessors anymore
371373
ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)(outerAccessorCtx)} in $treeCls")
372-
if (treeCls == toCls || outOfContext && toCls.derivesFrom(treeCls)) tree
374+
if (count == 0 || count < 0 && treeCls == toCls) tree
373375
else {
374376
val acc = outerAccessor(treeCls.asClass)(outerAccessorCtx)
375377
assert(acc.exists,
376378
i"failure to construct path from ${ctx.owner.ownersIterator.toList}%/% to `this` of ${toCls.showLocated};\n${treeCls.showLocated} does not have an outer accessor")
377-
loop(tree.select(acc).ensureApplied)
379+
loop(tree.select(acc).ensureApplied, count - 1)
378380
}
379381
}
380382
ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}")
381-
loop(start)
383+
loop(start, count)
382384
} catch {
383385
case ex: ClassCastException =>
384386
throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls")

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,10 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
440440
singleton(clazz.thisType)
441441
else if (ctx.owner.isConstructor)
442442
outerParam.get(ctx.owner) match {
443-
case Some(param) => outer.path(clazz, Ident(param.termRef))
444-
case _ => outer.path(clazz)
443+
case Some(param) => outer.path(start = Ident(param.termRef), toCls = clazz)
444+
case _ => outer.path(toCls = clazz)
445445
}
446-
else outer.path(clazz)
446+
else outer.path(toCls = clazz)
447447
transformFollowingDeep(qual.select(sym))
448448
}
449449

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -396,29 +396,29 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) {
396396
def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol
397397

398398
// The name of the outer selector that computes the rhs of `selfSym`
399-
def outerSelector(selfSym: Symbol): TermName = classOf(selfSym).name.toTermName ++ nme.OUTER_SELECT
399+
def outerSelector(n: Int): TermName = n.toString.toTermName ++ nme.OUTER_SELECT
400400

401401
// The total nesting depth of the class represented by `selfSym`.
402402
def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length
403403

404-
// All needed this-proxies, sorted by nesting depth of the classes they represent (innermost first)
405-
val accessedSelfSyms =
406-
thisProxy.toList.sortBy {
407-
case (cls, proxy) => -outerLevel(cls)
408-
} map {
409-
case (cls, proxy) => proxy.symbol
410-
}
404+
// All needed this-proxies, paired-with and sorted-by nesting depth of
405+
// the classes they represent (innermost first)
406+
val sortedProxies = thisProxy.toList.map {
407+
case (cls, proxy) => (outerLevel(cls), proxy.symbol)
408+
} sortBy (-_._1)
411409

412410
// Compute val-definitions for all this-proxies and append them to `bindingsBuf`
413411
var lastSelf: Symbol = NoSymbol
414-
for (selfSym <- accessedSelfSyms) {
412+
var lastLevel: Int = 0
413+
for ((level, selfSym) <- sortedProxies) {
415414
val rhs =
416415
if (!lastSelf.exists)
417416
prefix
418417
else
419-
untpd.Select(ref(lastSelf), outerSelector(selfSym)).withType(selfSym.info)
418+
untpd.Select(ref(lastSelf), outerSelector(lastLevel - level)).withType(selfSym.info)
420419
bindingsBuf += ValDef(selfSym.asTerm, rhs)
421420
lastSelf = selfSym
421+
lastLevel = level
422422
}
423423

424424
// The type map to apply to the inlined tree. This maps references to this-types

tests/run/i1990b.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
C()

tests/run/i1990b.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
trait A { self =>
2+
class Foo {
3+
inline def inlineMeth: Unit = {
4+
println(self)
5+
}
6+
}
7+
}
8+
9+
case class A2() extends A {
10+
case class C() extends Foo with A
11+
12+
val c = new C
13+
(new c.Foo).inlineMeth
14+
}
15+
16+
object Test {
17+
def main(args: Array[String]): Unit = {
18+
new A2
19+
}
20+
}

0 commit comments

Comments
 (0)