Skip to content

Commit 00eb80e

Browse files
authored
Merge pull request scala#6286 from retronym/topic/eager-default-getters
Eagerly enter default getters into scope
2 parents f5baf26 + befc337 commit 00eb80e

File tree

4 files changed

+187
-85
lines changed

4 files changed

+187
-85
lines changed

src/compiler/scala/tools/nsc/typechecker/MacroAnnotationNamers.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,13 @@ trait MacroAnnotationNamers { self: Analyzer =>
272272
if (isEnumConstant(tree))
273273
tree.symbol setInfo ConstantType(Constant(tree.symbol))
274274
case tree @ DefDef(_, nme.CONSTRUCTOR, _, _, _, _) =>
275+
if (mexists(tree.vparamss)(_.mods.hasDefault))
276+
enterDefaultGetters(tree.symbol, tree, tree.vparamss, tree.tparams)
275277
sym setInfo completerOf(tree)
276278
case tree @ DefDef(mods, name, tparams, _, _, _) =>
279+
if (mexists(tree.vparamss)(_.mods.hasDefault))
280+
enterDefaultGetters(tree.symbol, tree, tree.vparamss, tree.tparams)
281+
277282
val completer =
278283
if (sym hasFlag SYNTHETIC) {
279284
if (name == nme.copy) copyMethodCompleter(tree)

src/compiler/scala/tools/nsc/typechecker/Namers.scala

Lines changed: 156 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -745,21 +745,25 @@ trait Namers extends MethodSynthesis {
745745

746746
def enterTypeDef(tree: TypeDef) = assignAndEnterFinishedSymbol(tree)
747747

748-
def enterDefDef(tree: DefDef): Unit = tree match {
749-
case DefDef(_, nme.CONSTRUCTOR, _, _, _, _) =>
750-
assignAndEnterFinishedSymbol(tree)
751-
case DefDef(mods, name, _, _, _, _) =>
752-
val sym = enterInScope(assignMemberSymbol(tree))
748+
def enterDefDef(tree: DefDef): Unit = {
749+
tree match {
750+
case DefDef(_, nme.CONSTRUCTOR, _, _, _, _) =>
751+
assignAndEnterFinishedSymbol(tree)
752+
case DefDef(mods, name, _, _, _, _) =>
753+
val sym = enterInScope(assignMemberSymbol(tree))
753754

754-
val completer =
755-
if (sym hasFlag SYNTHETIC) {
756-
if (name == nme.copy) copyMethodCompleter(tree)
757-
else if (sym hasFlag CASE) applyUnapplyMethodCompleter(tree, context)
758-
else completerOf(tree)
759-
} else completerOf(tree)
755+
val completer =
756+
if (sym hasFlag SYNTHETIC) {
757+
if (name == nme.copy) copyMethodCompleter(tree)
758+
else if (sym hasFlag CASE) applyUnapplyMethodCompleter(tree, context)
759+
else completerOf(tree)
760+
} else completerOf(tree)
760761

761-
sym setInfo completer
762+
sym setInfo completer
762763
}
764+
if (mexists(tree.vparamss)(_.mods.hasDefault))
765+
enterDefaultGetters(tree.symbol, tree, tree.vparamss, tree.tparams)
766+
}
763767

764768
def enterClassDef(tree: ClassDef): Unit = {
765769
val ClassDef(mods, _, _, impl) = tree
@@ -1189,6 +1193,12 @@ trait Namers extends MethodSynthesis {
11891193
val module = clazz.sourceModule
11901194
for (cda <- module.attachments.get[ConstructorDefaultsAttachment]) {
11911195
debuglog(s"Storing the template namer in the ConstructorDefaultsAttachment of ${module.debugLocationString}.")
1196+
if (cda.defaults.nonEmpty) {
1197+
for (sym <- cda.defaults) {
1198+
decls.enter(sym)
1199+
}
1200+
cda.defaults.clear()
1201+
}
11921202
cda.companionModuleClassNamer = templateNamer
11931203
}
11941204
val classTp = ClassInfoType(parents, decls, clazz)
@@ -1419,6 +1429,42 @@ trait Namers extends MethodSynthesis {
14191429
pluginsTypeSig(methSig, typer, ddef, resTpGiven)
14201430
}
14211431

1432+
/**
1433+
* For every default argument, insert a method symbol computing that default
1434+
*/
1435+
def enterDefaultGetters(meth: Symbol, ddef: DefDef, vparamss: List[List[ValDef]], tparams: List[TypeDef]) {
1436+
val methOwner = meth.owner
1437+
val search = DefaultGetterNamerSearch(context, meth, initCompanionModule = false)
1438+
var posCounter = 1
1439+
1440+
mforeach(vparamss){(vparam) =>
1441+
// true if the corresponding parameter of the base class has a default argument
1442+
if (vparam.mods.hasDefault) {
1443+
val name = nme.defaultGetterName(meth.name, posCounter)
1444+
1445+
search.createAndEnter { owner: Symbol =>
1446+
methOwner.resetFlag(INTERFACE) // there's a concrete member now
1447+
val default = owner.newMethodSymbol(name, vparam.pos, paramFlagsToDefaultGetter(meth.flags))
1448+
default.setPrivateWithin(meth.privateWithin)
1449+
default.referenced = meth
1450+
default.setInfo(ErrorType)
1451+
if (meth.name == nme.apply && meth.hasAllFlags(CASE | SYNTHETIC)) {
1452+
val att = meth.attachments.get[CaseApplyDefaultGetters].getOrElse({
1453+
val a = new CaseApplyDefaultGetters()
1454+
meth.updateAttachment(a)
1455+
a
1456+
})
1457+
att.defaultGetters += default
1458+
}
1459+
if (default.owner.isTerm)
1460+
saveDefaultGetter(meth, default)
1461+
default
1462+
}
1463+
}
1464+
posCounter += 1
1465+
}
1466+
}
1467+
14221468
/**
14231469
* For every default argument, insert a method computing that default
14241470
*
@@ -1433,6 +1479,7 @@ trait Namers extends MethodSynthesis {
14331479
// in methods with multiple default parameters
14341480
def rtparams = rtparams0.map(_.duplicate)
14351481
def rvparamss = rvparamss0.map(_.map(_.duplicate))
1482+
val search = DefaultGetterNamerSearch(context, meth, initCompanionModule = true)
14361483
val methOwner = meth.owner
14371484
val isConstr = meth.isConstructor
14381485
val overrides = overridden != NoSymbol && !overridden.isOverloaded
@@ -1448,9 +1495,6 @@ trait Namers extends MethodSynthesis {
14481495
"" + meth.fullName + ", "+ overridden.fullName
14491496
)
14501497

1451-
// cache the namer used for entering the default getter symbols
1452-
var ownerNamer: Option[Namer] = None
1453-
var moduleNamer: Option[(ClassDef, Namer)] = None
14541498
var posCounter = 1
14551499

14561500
// For each value parameter, create the getter method if it has a
@@ -1490,80 +1534,46 @@ trait Namers extends MethodSynthesis {
14901534
val oflag = if (baseHasDefault) OVERRIDE else 0
14911535
val name = nme.defaultGetterName(meth.name, posCounter)
14921536

1493-
var defTparams = rtparams
14941537
val defVparamss = mmap(rvparamss.take(previous.length)){ rvp =>
14951538
copyValDef(rvp)(mods = rvp.mods &~ DEFAULTPARAM, rhs = EmptyTree)
14961539
}
1497-
1498-
val parentNamer = if (isConstr) {
1499-
val (cdef, nmr) = moduleNamer.getOrElse {
1500-
val module = companionSymbolOf(methOwner, context)
1501-
module.initialize // call type completer (typedTemplate), adds the
1502-
// module's templateNamer to classAndNamerOfModule
1503-
module.attachments.get[ConstructorDefaultsAttachment] match {
1504-
// by martin: the null case can happen in IDE; this is really an ugly hack on top of an ugly hack but it seems to work
1505-
case Some(cda) =>
1506-
if (cda.companionModuleClassNamer == null) {
1507-
devWarning(s"scala/bug#6576 The companion module namer for $meth was unexpectedly null")
1508-
return
1509-
}
1510-
val p = (cda.classWithDefault, cda.companionModuleClassNamer)
1511-
moduleNamer = Some(p)
1512-
p
1513-
case _ =>
1514-
return // fix #3649 (prevent crash in erroneous source code)
1540+
search.addGetter(rtparams) {
1541+
(parentNamer: Namer, defTparams: List[TypeDef]) =>
1542+
val defTpt =
1543+
// don't mess with tpt's of case copy default getters, because assigning something other than TypeTree()
1544+
// will break the carefully orchestrated naming/typing logic that involves copyMethodCompleter and caseClassCopyMeth
1545+
if (meth.isCaseCopy) TypeTree()
1546+
else {
1547+
// If the parameter type mentions any type parameter of the method, let the compiler infer the
1548+
// return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
1549+
// This is better than always using Wildcard for inferring the result type, for example in
1550+
// def f(i: Int, m: Int => Int = identity _) = m(i)
1551+
// if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
1552+
// TODO: this is a very brittle approach; I sincerely hope that Denys's research into hygiene
1553+
// will open the doors to a much better way of doing this kind of stuff
1554+
val tparamNames = defTparams map { case TypeDef(_, name, _, _) => name }
1555+
val eraseAllMentionsOfTparams = new TypeTreeSubstituter(tparamNames contains _)
1556+
eraseAllMentionsOfTparams(rvparam.tpt match {
1557+
// default getter for by-name params
1558+
case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
1559+
case t => t
1560+
})
1561+
}
1562+
val defRhs = rvparam.rhs
1563+
1564+
val defaultTree = atPos(vparam.pos.focus) {
1565+
DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags), ddef.mods.privateWithin) | oflag, name, defTparams, defVparamss, defTpt, defRhs)
15151566
}
1516-
}
1517-
val ClassDef(_, _, rtparams, _) = resetAttrs(deriveClassDef(cdef)(_ => Template(Nil, noSelfType, Nil)).duplicate)
1518-
defTparams = rtparams.map(rt => copyTypeDef(rt)(mods = rt.mods &~ (COVARIANT | CONTRAVARIANT)))
1519-
nmr
1520-
}
1521-
else ownerNamer getOrElse {
1522-
val ctx = context.nextEnclosing(c => c.scope.toList.contains(meth))
1523-
assert(ctx != NoContext, meth)
1524-
val nmr = newNamer(ctx)
1525-
ownerNamer = Some(nmr)
1526-
nmr
1527-
}
1528-
1529-
val defTpt =
1530-
// don't mess with tpt's of case copy default getters, because assigning something other than TypeTree()
1531-
// will break the carefully orchestrated naming/typing logic that involves copyMethodCompleter and caseClassCopyMeth
1532-
if (meth.isCaseCopy) TypeTree()
1533-
else {
1534-
// If the parameter type mentions any type parameter of the method, let the compiler infer the
1535-
// return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
1536-
// This is better than always using Wildcard for inferring the result type, for example in
1537-
// def f(i: Int, m: Int => Int = identity _) = m(i)
1538-
// if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
1539-
// TODO: this is a very brittle approach; I sincerely hope that Denys's research into hygiene
1540-
// will open the doors to a much better way of doing this kind of stuff
1541-
val tparamNames = defTparams map { case TypeDef(_, name, _, _) => name }
1542-
val eraseAllMentionsOfTparams = new TypeTreeSubstituter(tparamNames contains _)
1543-
eraseAllMentionsOfTparams(rvparam.tpt match {
1544-
// default getter for by-name params
1545-
case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
1546-
case t => t
1547-
})
1548-
}
1549-
val defRhs = rvparam.rhs
1550-
1551-
val defaultTree = atPos(vparam.pos.focus) {
1552-
DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags), ddef.mods.privateWithin) | oflag, name, defTparams, defVparamss, defTpt, defRhs)
1553-
}
1554-
if (!isConstr)
1555-
methOwner.resetFlag(INTERFACE) // there's a concrete member now
1556-
val default = parentNamer.enterSyntheticSym(defaultTree)
1557-
if (meth.name == nme.apply && meth.hasAllFlags(CASE | SYNTHETIC)) {
1558-
val att = meth.attachments.get[CaseApplyDefaultGetters].getOrElse({
1559-
val a = new CaseApplyDefaultGetters()
1560-
meth.updateAttachment(a)
1561-
a
1562-
})
1563-
att.defaultGetters += default
1567+
def referencesThis(sym: Symbol) = sym match {
1568+
case term: TermSymbol => term.referenced == meth
1569+
case _ => false
1570+
}
1571+
val defaultGetterSym = parentNamer.context.scope.lookup(name).filter(referencesThis)
1572+
assert(defaultGetterSym != NoSymbol, (parentNamer.owner, name))
1573+
defaultTree.setSymbol(defaultGetterSym)
1574+
defaultGetterSym.setInfo(parentNamer.completerOf(defaultTree))
1575+
defaultTree
15641576
}
1565-
if (default.owner.isTerm)
1566-
saveDefaultGetter(meth, default)
15671577
}
15681578
else if (baseHasDefault) {
15691579
// the parameter does not have a default itself, but the
@@ -1578,6 +1588,68 @@ trait Namers extends MethodSynthesis {
15781588
}
15791589
}
15801590

1591+
private object DefaultGetterNamerSearch {
1592+
def apply(c: Context, meth: Symbol, initCompanionModule: Boolean) = if (meth.isConstructor) new DefaultGetterInCompanion(c, meth, initCompanionModule)
1593+
else new DefaultMethodInOwningScope(c, meth)
1594+
}
1595+
private abstract class DefaultGetterNamerSearch {
1596+
def addGetter(rtparams0: List[TypeDef])(create: (Namer, List[TypeDef]) => Tree)
1597+
1598+
def createAndEnter(f: Symbol => Symbol): Unit
1599+
}
1600+
private class DefaultGetterInCompanion(c: Context, meth: Symbol, initCompanionModule: Boolean) extends DefaultGetterNamerSearch {
1601+
private val module = companionSymbolOf(meth.owner, context)
1602+
if (initCompanionModule) module.initialize
1603+
private val cda: Option[ConstructorDefaultsAttachment] = module.attachments.get[ConstructorDefaultsAttachment]
1604+
private val moduleNamer = cda.flatMap(x => Option(x.companionModuleClassNamer))
1605+
1606+
def createAndEnter(f: Symbol => Symbol): Unit = {
1607+
val default = f(module.moduleClass)
1608+
moduleNamer match {
1609+
case Some(namer) =>
1610+
namer.enterInScope(default)
1611+
case None =>
1612+
cda match {
1613+
case Some(attachment) =>
1614+
// defer entry until the companion module body it type completed
1615+
attachment.defaults += default
1616+
case None =>
1617+
// ignore error to fix #3649 (prevent crash in erroneous source code)
1618+
}
1619+
}
1620+
}
1621+
def addGetter(rtparams0: List[TypeDef])(create: (Namer, List[TypeDef]) => Tree): Unit = {
1622+
cda match {
1623+
case Some(attachment) =>
1624+
moduleNamer match {
1625+
case Some(namer) =>
1626+
val cdef = attachment.classWithDefault
1627+
val ClassDef(_, _, rtparams, _) = resetAttrs(deriveClassDef(cdef)(_ => Template(Nil, noSelfType, Nil)).duplicate)
1628+
val defTparams = rtparams.map(rt => copyTypeDef(rt)(mods = rt.mods &~ (COVARIANT | CONTRAVARIANT)))
1629+
val tree = create(namer, defTparams)
1630+
namer.enterSyntheticSym(tree)
1631+
case None =>
1632+
}
1633+
case None =>
1634+
}
1635+
1636+
}
1637+
}
1638+
private class DefaultMethodInOwningScope(c: Context, meth: Symbol) extends DefaultGetterNamerSearch {
1639+
private lazy val ownerNamer: Namer = {
1640+
val ctx = context.nextEnclosing(c => c.scope.toList.contains(meth)) // TODO use lookup rather than toList.contains
1641+
assert(ctx != NoContext, meth)
1642+
newNamer(ctx)
1643+
}
1644+
def createAndEnter(f: Symbol => Symbol): Unit = {
1645+
ownerNamer.enterInScope(f(ownerNamer.context.owner))
1646+
}
1647+
def addGetter(rtparams0: List[TypeDef])(create: (Namer, List[TypeDef]) => Tree): Unit = {
1648+
val tree = create(ownerNamer, rtparams0)
1649+
ownerNamer.enterSyntheticSym(tree)
1650+
}
1651+
}
1652+
15811653
private def valDefSig(vdef: ValDef) = {
15821654
val ValDef(_, _, tpt, rhs) = vdef
15831655
val result =

src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ trait NamesDefaults { self: Analyzer =>
2727
// we need the ClassDef. To create and enter the symbols into the companion
2828
// object, we need the templateNamer of that module class. These two are stored
2929
// as an attachment in the companion module symbol
30-
class ConstructorDefaultsAttachment(val classWithDefault: ClassDef, var companionModuleClassNamer: Namer)
30+
class ConstructorDefaultsAttachment(val classWithDefault: ClassDef, var companionModuleClassNamer: Namer) {
31+
val defaults = mutable.ListBuffer[Symbol]()
32+
}
3133

3234
// Attached to the synthetic companion `apply` method symbol generated for case classes, holds
3335
// the set contains all default getters for that method. If the synthetic `apply` is unlinked in
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package scala.tools.nsc.typechecker
2+
3+
import org.junit.{Assert, Test}
4+
import org.junit.runner.RunWith
5+
import org.junit.runners.JUnit4
6+
7+
import scala.tools.testing.BytecodeTesting
8+
9+
@RunWith(classOf[JUnit4])
10+
class NamerTest extends BytecodeTesting {
11+
12+
import compiler.global._
13+
14+
override def compilerArgs: String = "-Ystop-after:typer"
15+
16+
@Test
17+
def defaultMethodsInDeclarationOrder(): Unit = {
18+
compiler.compileClasses("package p1; class Test { C.b(); C.a() }; object C { def a(x: Int = 0) = 0; def b(x: Int = 0) = 0 }")
19+
val methods = compiler.global.rootMirror.getRequiredModule("p1.C").info.decls.toList.map(_.name.toString).filter(_.matches("""(a|b).*"""))
20+
def getterName(s: String) = nme.defaultGetterName(TermName(s), 1).toString
21+
Assert.assertEquals(List("a", getterName("a"), "b", getterName("b")), methods) // order no longer depends on order of lazy type completion :)
22+
}
23+
}

0 commit comments

Comments
 (0)