Skip to content

Commit 027abb4

Browse files
committed
Merge pull request #69 from odersky/topic/generalize-companions
Bullet-proofing companion objects
2 parents 6a97264 + 1554fdd commit 027abb4

File tree

8 files changed

+143
-18
lines changed

8 files changed

+143
-18
lines changed

src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
276276
}
277277
}
278278

279-
trait TypedTreeInfo extends TreeInfo[Type] {self: Trees.Instance[Type] =>
279+
trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] =>
280+
// todo: fill with methods from TreeInfo that only apply to untpd.Tree's
281+
}
282+
283+
trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
280284

281285
/** Is tree a definition that has no side effects when
282286
* evaluated as part of a block after the first time?
@@ -407,11 +411,67 @@ trait TypedTreeInfo extends TreeInfo[Type] {self: Trees.Instance[Type] =>
407411
false
408412
}
409413

414+
/** If `tree` is a DefTree, the symbol defined by it, otherwise NoSymbol */
415+
def definedSym(tree: Tree)(implicit ctx: Context): Symbol =
416+
if (tree.isDef) tree.symbol else NoSymbol
417+
418+
/** Going from child to parent, the path of tree nodes that starts
419+
* with a definition of symbol `sym` and ends with `root`, or Nil
420+
* if no such path exists.
421+
* Pre: `sym` must have a position.
422+
*/
423+
def defPath(sym: Symbol, root: Tree)(implicit ctx: Context): List[Tree] = ctx.debugTraceIndented(s"defpath($sym with position ${sym.pos}, ${root.show})") {
424+
def show(from: Any): String = from match {
425+
case tree: Trees.Tree[_] => s"${tree.show} with attachments ${tree.allAttachments}"
426+
case x: printing.Showable => x.show
427+
case x => x.toString
428+
}
429+
430+
def search(from: Any): List[Tree] = ctx.debugTraceIndented(s"search(${show(from)})") {
431+
from match {
432+
case tree: Tree => // Dotty problem: cannot write Tree @ unchecked, this currently gives a syntax error
433+
if (definedSym(tree) == sym) tree :: Nil
434+
else if (tree.envelope.contains(sym.pos)) {
435+
val p = search(tree.productIterator)
436+
if (p.isEmpty) p else tree :: p
437+
} else Nil
438+
case xs: Iterable[_] =>
439+
search(xs.iterator)
440+
case xs: Iterator[_] =>
441+
xs.map(search).find(_.nonEmpty).getOrElse(Nil)
442+
case _ =>
443+
Nil
444+
}
445+
}
446+
require(sym.pos.exists)
447+
search(root)
448+
}
449+
450+
/** The statement sequence that contains a definition of `sym`, or Nil
451+
* if none was found.
452+
* For a tree to be found, The symbol must have a position and its definition
453+
* tree must be reachable from come tree stored in an enclosing context.
454+
*/
455+
def definingStats(sym: Symbol)(implicit ctx: Context): List[Tree] =
456+
if (!sym.pos.exists || (ctx eq NoContext) || ctx.compilationUnit == null) Nil
457+
else defPath(sym, ctx.compilationUnit.tpdTree) match {
458+
case defn :: encl :: _ =>
459+
def verify(stats: List[Tree]) =
460+
if (stats exists (definedSym(_) == sym)) stats else Nil
461+
encl match {
462+
case Block(stats, _) => verify(stats)
463+
case Template(_, _, _, stats) => verify(stats)
464+
case PackageDef(_, stats) => verify(stats)
465+
case _ => Nil
466+
}
467+
case nil =>
468+
Nil
469+
}
470+
}
471+
410472
/** a Match(Typed(_, tpt), _) must be translated into a switch if isSwitchAnnotation(tpt.tpe)
411473
def isSwitchAnnotation(tpe: Type) = tpe hasAnnotation defn.SwitchClass
412474
*/
413-
}
414-
415475

416476
/** Does list of trees start with a definition of
417477
* a class of module with given name (ignoring imports)

src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Decorators._
99
import language.higherKinds
1010
import collection.mutable.ListBuffer
1111

12-
object untpd extends Trees.Instance[Untyped] with TreeInfo[Untyped] {
12+
object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
1313

1414
// ----- Tree cases that exist in untyped form only ------------------
1515

src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import collection.mutable
99
import collection.immutable.BitSet
1010
import scala.reflect.io.AbstractFile
1111
import Decorators.SymbolIteratorDecorator
12+
import ast.tpd
1213
import annotation.tailrec
1314
import util.SimpleMap
1415
import util.Stats
@@ -601,22 +602,42 @@ object SymDenotations {
601602
* NoSymbol if this module does not exist.
602603
*/
603604
final def companionModule(implicit ctx: Context): Symbol =
604-
if (owner.exists && name != tpnme.ANON_CLASS) // name test to avoid forcing, thereby causing cyclic reference errors
605-
owner.info.decl(effectiveName.toTermName)
606-
.suchThat(sym => (sym is Module) && sym.isCoDefinedWith(symbol))
607-
.symbol
608-
else NoSymbol
605+
if (name == tpnme.ANON_CLASS)
606+
NoSymbol // avoid forcing anon classes, this might cause cyclic reference errors
607+
else
608+
companionNamed(effectiveName.moduleClassName).sourceModule
609609

610610
/** The class with the same (type-) name as this module or module class,
611611
* and which is also defined in the same scope and compilation unit.
612612
* NoSymbol if this class does not exist.
613613
*/
614614
final def companionClass(implicit ctx: Context): Symbol =
615-
if (owner.exists)
616-
owner.info.decl(effectiveName.toTypeName)
617-
.suchThat(sym => sym.isClass && sym.isCoDefinedWith(symbol))
618-
.symbol
619-
else NoSymbol
615+
companionNamed(effectiveName.toTypeName)
616+
617+
/** Find companion class symbol with given name, or NoSymbol if none exists.
618+
* Three alternative strategies:
619+
* 1. If owner is a class, look in its members, otherwise
620+
* 2. If current compilation unit has a typed tree,
621+
* determine the definining statement sequence and search its trees, otherwise
622+
* 3. If context has an enclosing scope which defines this symbol,
623+
* lookup its companion in the same scope.
624+
*/
625+
private def companionNamed(name: TypeName)(implicit ctx: Context): Symbol =
626+
if (owner.isClass)
627+
owner.info.decl(name).suchThat(_.isCoDefinedWith(symbol)).symbol
628+
else if (!owner.exists || ctx.compilationUnit == null)
629+
NoSymbol
630+
else if (!ctx.compilationUnit.tpdTree.isEmpty)
631+
tpd.definingStats(symbol).iterator
632+
.map(tpd.definedSym)
633+
.find(_.name == name)
634+
.getOrElse(NoSymbol)
635+
else if (ctx.scope == null)
636+
NoSymbol
637+
else if (ctx.scope.lookup(this.name) == symbol)
638+
ctx.scope.lookup(name)
639+
else
640+
companionNamed(name)(ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next)
620641

621642
/** If this is a class, the module class of its companion object.
622643
* If this is a module class, its companion class.

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import StdNames._
2222
import ProtoTypes._
2323
import EtaExpansion._
2424
import collection.mutable
25-
import reflect.ClassTag
2625
import config.Printers._
2726
import TypeApplications._
2827
import language.implicitConversions

src/dotty/tools/dotc/util/Attachment.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ object Attachment {
7474
else nx.removeAttachment(key)
7575
}
7676

77-
/** The list of all values attached to this container. */
78-
final def allAttachments: List[Any] = {
77+
/** The list of all keys and values attached to this container. */
78+
final def allAttachments: List[(Key[_], Any)] = {
7979
val nx = next
80-
if (nx == null) Nil else nx.value :: nx.allAttachments
80+
if (nx == null) Nil else (nx.key, nx.value) :: nx.allAttachments
8181
}
8282
}
8383

test/dotc/tests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class tests extends CompilerTest {
5656
@Test def neg_templateParents() = compileFile(negDir, "templateParents", xerrors = 3)
5757
@Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1)
5858
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4)
59+
@Test def neg_companions = compileFile(negDir, "companions", xerrors = 1)
5960

6061
@Test def dotc = compileDir(dotcDir + "tools/dotc", twice)
6162
@Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice)

tests/neg/companions.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
object companionsNeg {
2+
3+
def foo() = {
4+
5+
class C {
6+
private val q = 2
7+
}
8+
9+
{ object C {
10+
private val p = 1
11+
println(new C().q)
12+
}}
13+
}
14+
15+
}
16+

tests/pos/companions.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
object companions {
2+
3+
def foo() = {
4+
5+
class C {
6+
println(C.p)
7+
private val q = 2
8+
}
9+
10+
object C {
11+
private val p = 1
12+
println(new C().q)
13+
}
14+
}
15+
16+
}
17+
object companions2 {
18+
def foo() = {
19+
{
20+
class C {
21+
println(C.p)
22+
}
23+
object C {
24+
private val p = 1
25+
}
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)