Skip to content

Commit cafeb34

Browse files
committed
Merge pull request scala#3305 from xeno-by/topic/copy-untyped
awakens default getter synthesis from the untyper nightmare
2 parents 527fd9a + 5f08c78 commit cafeb34

File tree

7 files changed

+102
-49
lines changed

7 files changed

+102
-49
lines changed

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

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ trait Namers extends MethodSynthesis {
11321132
}
11331133
}
11341134

1135-
addDefaultGetters(meth, vparamss, tparams, overriddenSymbol(methResTp))
1135+
addDefaultGetters(meth, ddef, vparamss, tparams, overriddenSymbol(methResTp))
11361136

11371137
// fast track macros, i.e. macros defined inside the compiler, are hardcoded
11381138
// hence we make use of that and let them have whatever right-hand side they need
@@ -1174,7 +1174,12 @@ trait Namers extends MethodSynthesis {
11741174
* typechecked, the corresponding param would not yet have the "defaultparam"
11751175
* flag.
11761176
*/
1177-
private def addDefaultGetters(meth: Symbol, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) {
1177+
private def addDefaultGetters(meth: Symbol, ddef: DefDef, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) {
1178+
val DefDef(_, _, rtparams0, rvparamss0, _, _) = resetLocalAttrs(ddef.duplicate)
1179+
// having defs here is important to make sure that there's no sneaky tree sharing
1180+
// in methods with multiple default parameters
1181+
def rtparams = rtparams0.map(_.duplicate)
1182+
def rvparamss = rvparamss0.map(_.map(_.duplicate))
11781183
val methOwner = meth.owner
11791184
val isConstr = meth.isConstructor
11801185
val overridden = if (isConstr || !methOwner.isClass) NoSymbol else overriddenSymbol
@@ -1206,23 +1211,36 @@ trait Namers extends MethodSynthesis {
12061211
//
12071212
vparamss.foldLeft(Nil: List[List[ValDef]]) { (previous, vparams) =>
12081213
assert(!overrides || vparams.length == baseParamss.head.length, ""+ meth.fullName + ", "+ overridden.fullName)
1214+
val rvparams = rvparamss(previous.length)
12091215
var baseParams = if (overrides) baseParamss.head else Nil
1210-
for (vparam <- vparams) {
1216+
map2(vparams, rvparams)((vparam, rvparam) => {
12111217
val sym = vparam.symbol
12121218
// true if the corresponding parameter of the base class has a default argument
12131219
val baseHasDefault = overrides && baseParams.head.hasDefault
12141220
if (sym.hasDefault) {
1215-
// generate a default getter for that argument
1221+
// Create a "default getter", i.e. a DefDef that will calculate vparam.rhs
1222+
// for those who are going to call meth without providing an argument corresponding to vparam.
1223+
// After the getter is created, a corresponding synthetic symbol is created and entered into the parent namer.
1224+
//
1225+
// In the ideal world, this DefDef would be a simple one-liner that just returns vparam.rhs,
1226+
// but in scalac things are complicated in two different ways.
1227+
//
1228+
// 1) Because the underlying language is quite sophisticated, we must allow for those sophistications in our getter.
1229+
// Namely: a) our getter has to copy type parameters from the associated method (or the associated class
1230+
// if meth is a constructor), because vparam.rhs might refer to one of them, b) our getter has to copy
1231+
// preceding value parameter lists from the associated method, because again vparam.rhs might refer to one of them.
1232+
//
1233+
// 2) Because we have already assigned symbols to type and value parameters that we have to copy, we must jump through
1234+
// hoops in order to destroy them and allow subsequent naming create new symbols for our getter. Previously this
1235+
// was done in an overly brutal way akin to resetAllAttrs, but now we utilize a resetLocalAttrs-based approach.
1236+
// Still far from ideal, but at least enables things like run/macro-default-params that were previously impossible.
1237+
12161238
val oflag = if (baseHasDefault) OVERRIDE else 0
12171239
val name = nme.defaultGetterName(meth.name, posCounter)
12181240

1219-
// Create trees for the defaultGetter. Uses tools from Unapplies.scala
1220-
var deftParams = tparams map copyUntyped[TypeDef]
1221-
val defvParamss = mmap(previous) { p =>
1222-
// in the default getter, remove the default parameter
1223-
val p1 = atPos(p.pos.focus) { ValDef(p.mods &~ DEFAULTPARAM, p.name, p.tpt.duplicate, EmptyTree) }
1224-
UnTyper.traverse(p1)
1225-
p1
1241+
var defTparams = rtparams
1242+
val defVparamss = mmap(rvparamss.take(previous.length)){ rvp =>
1243+
copyValDef(rvp)(mods = rvp.mods &~ DEFAULTPARAM, rhs = EmptyTree)
12261244
}
12271245

12281246
val parentNamer = if (isConstr) {
@@ -1244,7 +1262,8 @@ trait Namers extends MethodSynthesis {
12441262
return // fix #3649 (prevent crash in erroneous source code)
12451263
}
12461264
}
1247-
deftParams = cdef.tparams map copyUntypedInvariant
1265+
val ClassDef(_, _, rtparams, _) = resetLocalAttrs(cdef.duplicate)
1266+
defTparams = rtparams.map(rt => copyTypeDef(rt)(mods = rt.mods &~ (COVARIANT | CONTRAVARIANT)))
12481267
nmr
12491268
}
12501269
else ownerNamer getOrElse {
@@ -1255,23 +1274,30 @@ trait Namers extends MethodSynthesis {
12551274
nmr
12561275
}
12571276

1258-
// If the parameter type mentions any type parameter of the method, let the compiler infer the
1259-
// return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
1260-
// This is better than always using Wildcard for inferring the result type, for example in
1261-
// def f(i: Int, m: Int => Int = identity _) = m(i)
1262-
// if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
1263-
val names = deftParams map { case TypeDef(_, name, _, _) => name }
1264-
val subst = new TypeTreeSubstituter(names contains _)
1265-
1266-
val defTpt = subst(copyUntyped(vparam.tpt match {
1267-
// default getter for by-name params
1268-
case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
1269-
case t => t
1270-
}))
1271-
val defRhs = copyUntyped(vparam.rhs)
1277+
val defTpt =
1278+
// don't mess with tpt's of case copy default getters, because assigning something other than TypeTree()
1279+
// will break the carefully orchestrated naming/typing logic that involves enterCopyMethod and caseClassCopyMeth
1280+
if (meth.isCaseCopy) TypeTree()
1281+
else {
1282+
// If the parameter type mentions any type parameter of the method, let the compiler infer the
1283+
// return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
1284+
// This is better than always using Wildcard for inferring the result type, for example in
1285+
// def f(i: Int, m: Int => Int = identity _) = m(i)
1286+
// if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
1287+
// TODO: this is a very brittle approach; I sincerely hope that Denys's research into hygiene
1288+
// will open the doors to a much better way of doing this kind of stuff
1289+
val tparamNames = defTparams map { case TypeDef(_, name, _, _) => name }
1290+
val eraseAllMentionsOfTparams = new TypeTreeSubstituter(tparamNames contains _)
1291+
eraseAllMentionsOfTparams(rvparam.tpt match {
1292+
// default getter for by-name params
1293+
case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
1294+
case t => t
1295+
})
1296+
}
1297+
val defRhs = rvparam.rhs
12721298

12731299
val defaultTree = atPos(vparam.pos.focus) {
1274-
DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, deftParams, defvParamss, defTpt, defRhs)
1300+
DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, defTparams, defVparamss, defTpt, defRhs)
12751301
}
12761302
if (!isConstr)
12771303
methOwner.resetFlag(INTERFACE) // there's a concrete member now
@@ -1286,7 +1312,7 @@ trait Namers extends MethodSynthesis {
12861312
}
12871313
posCounter += 1
12881314
if (overrides) baseParams = baseParams.tail
1289-
}
1315+
})
12901316
if (overrides) baseParamss = baseParamss.tail
12911317
previous :+ vparams
12921318
}

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
5656
resetDocComments()
5757
}
5858

59-
object UnTyper extends Traverser {
60-
override def traverse(tree: Tree) = {
61-
if (tree.canHaveAttrs) {
62-
tree.clearType()
63-
if (tree.hasSymbolField) tree.symbol = NoSymbol
64-
}
65-
super.traverse(tree)
66-
}
67-
}
68-
6959
sealed abstract class SilentResult[+T] {
7060
@inline final def fold[U](none: => U)(f: T => U): U = this match {
7161
case SilentResultValue(value) => f(value)

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,6 @@ trait Unapplies extends ast.TreeDSL {
4343
def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption
4444
}
4545

46-
def copyUntyped[T <: Tree](tree: T): T =
47-
returning[T](tree.duplicate)(UnTyper traverse _)
48-
49-
def copyUntypedInvariant(td: TypeDef): TypeDef =
50-
copyTypeDef(copyUntyped(td))(mods = td.mods &~ (COVARIANT | CONTRAVARIANT))
51-
5246
private def toIdent(x: DefTree) = Ident(x.name) setPos x.pos.focus
5347

5448
private def classType(cdef: ClassDef, tparams: List[TypeDef]): Tree = {
@@ -58,8 +52,15 @@ trait Unapplies extends ast.TreeDSL {
5852
}
5953

6054
private def constrParamss(cdef: ClassDef): List[List[ValDef]] = {
61-
val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor cdef.impl.body
62-
mmap(vparamss)(copyUntyped[ValDef])
55+
val ClassDef(_, _, _, Template(_, _, body)) = resetLocalAttrs(cdef.duplicate)
56+
val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor body
57+
vparamss
58+
}
59+
60+
private def constrTparamsInvariant(cdef: ClassDef): List[TypeDef] = {
61+
val ClassDef(_, _, tparams, _) = resetLocalAttrs(cdef.duplicate)
62+
val tparamsInvariant = tparams.map(tparam => copyTypeDef(tparam)(mods = tparam.mods &~ (COVARIANT | CONTRAVARIANT)))
63+
tparamsInvariant
6364
}
6465

6566
/** The return value of an unapply method of a case class C[Ts]
@@ -125,7 +126,7 @@ trait Unapplies extends ast.TreeDSL {
125126
/** The apply method corresponding to a case class
126127
*/
127128
def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = {
128-
val tparams = cdef.tparams map copyUntypedInvariant
129+
val tparams = constrTparamsInvariant(cdef)
129130
val cparamss = constrParamss(cdef)
130131
def classtpe = classType(cdef, tparams)
131132
atPos(cdef.pos.focus)(
@@ -141,7 +142,7 @@ trait Unapplies extends ast.TreeDSL {
141142
/** The unapply method corresponding to a case class
142143
*/
143144
def caseModuleUnapplyMeth(cdef: ClassDef): DefDef = {
144-
val tparams = cdef.tparams map copyUntypedInvariant
145+
val tparams = constrTparamsInvariant(cdef)
145146
val method = constrParamss(cdef) match {
146147
case xs :: _ if xs.nonEmpty && isRepeatedParamType(xs.last.tpt) => nme.unapplySeq
147148
case _ => nme.unapply
@@ -196,7 +197,7 @@ trait Unapplies extends ast.TreeDSL {
196197
treeCopy.ValDef(vd, Modifiers(flags), vd.name, tpt, rhs)
197198
}
198199

199-
val tparams = cdef.tparams map copyUntypedInvariant
200+
val tparams = constrTparamsInvariant(cdef)
200201
val paramss = classParamss match {
201202
case Nil => Nil
202203
case ps :: pss =>

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,9 +801,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
801801
isConstructor && !isPrimaryConstructor
802802

803803
/** Is this symbol a synthetic apply or unapply method in a companion object of a case class? */
804+
// xeno-by: why this obscure use of the CASE flag? why not simply compare name with nme.apply and nme.unapply?
804805
final def isCaseApplyOrUnapply =
805806
isMethod && isCase && isSynthetic
806807

808+
/** Is this symbol a synthetic copy method in a case class? */
809+
final def isCaseCopy =
810+
isMethod && owner.isCase && isSynthetic && name == nme.copy
811+
807812
/** Is this symbol a trait which needs an implementation class? */
808813
final def needsImplClass = (
809814
isTrait
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros.WhiteboxContext
3+
4+
object Macros {
5+
def id[A]: A = null.asInstanceOf[A]
6+
7+
def foo: Any = macro impl
8+
def impl(c: WhiteboxContext): c.Tree = {
9+
import c.universe._
10+
import Flag._
11+
12+
lazy val tpe = TypeTree(typeOf[Int])
13+
14+
/* If we used this line instead, it would work! */
15+
// lazy val tpe = tq"Int"
16+
17+
lazy val param: ValDef = {
18+
val p1 = q"val a: ${tpe.duplicate} = Macros.id[${tpe.duplicate}]"
19+
ValDef(Modifiers(DEFAULTPARAM), p1.name, p1.tpt, p1.rhs)
20+
}
21+
22+
q"""
23+
class C { def f($param) = a }
24+
println(new C().f())
25+
"""
26+
}
27+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test extends App {
2+
Macros.foo
3+
}

0 commit comments

Comments
 (0)