Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

awakens default getter synthesis from the untyper nightmare #3305

Merged
merged 2 commits into from

3 participants

Eugene Burmako soc Jason Zaugg
Eugene Burmako
Owner

Our happy little macro paradise is regularly invaded by resetAllAttrs,
the bane of all macros and typers. It’s so ruthless and devastating that
we’ve been long scheming to hack something really cool and to one day
defeat it.

Today we make the first step towards the happy future. Today we overthrow
the UnTyper, resetAllAttrs’s elder brother that rules in the kingdoms of
GetterLand and CaseClassia, and banish him from the land of getters.
In the name of what’s good and meta, let’s band together and completely
drive him away in a subsequent pull request! (upd. Done - see the second commit).

Jason Zaugg retronym was assigned
Eugene Burmako
Owner

review by Sir @Retronym

soc

Wow, impressive! Nice work!

Jason Zaugg retronym commented on the diff
src/reflect/scala/reflect/internal/Symbols.scala
@@ -801,9 +801,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
isConstructor && !isPrimaryConstructor
/** Is this symbol a synthetic apply or unapply method in a companion object of a case class? */
+ // xeno-by: why this obscure use of the CASE flag? why not simply compare name with nme.apply and nme.unapply?
Jason Zaugg Owner

Case companions can also have user defined apply methods in addition to the synthetic one.

Eugene Burmako Owner
xeno-by added a note

But user-defined apply methods don't have the SYNTHETIC flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jason Zaugg
Owner

Could you please explain the first commit more thoroughly? What was the essential things you changed to make this work? What is the pre-existing test coverage like for tricky default getters?

src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -1255,23 +1272,28 @@ trait Namers extends MethodSynthesis {
nmr
}
- // If the parameter type mentions any type parameter of the method, let the compiler infer the
- // return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
- // This is better than always using Wildcard for inferring the result type, for example in
- // def f(i: Int, m: Int => Int = identity _) = m(i)
- // if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
- val names = deftParams map { case TypeDef(_, name, _, _) => name }
- val subst = new TypeTreeSubstituter(names contains _)
-
- val defTpt = subst(copyUntyped(vparam.tpt match {
- // default getter for by-name params
- case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
- case t => t
- }))
- val defRhs = copyUntyped(vparam.rhs)
+ val defTpt = // don't mess with tpt's of case copy default getters unless you know better
Jason Zaugg Owner

"... otherwise test tNNNN.scala will break"

Eugene Burmako Owner
xeno-by added a note

It's not a particular test - if you don't set TypeTree() here, then every synthetic copy will stop working.

Jason Zaugg Owner
retronym added a note

My general point was that comments like "unless you know better" can by super frustrating to read when maintaining code. We should always seek instead to explain a) what will break without this, and (if possible) b) what the underlying reason is for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Eugene Burmako
Owner

@retronym To put it in a nutshell, instead of using untyper on a DefDef to do default getter synthesis, the commit duplicates the DefDef and does resetLocalAttrs on it.

As default getter synthesis proceeds with figuring out type and value parameters for the getter, then its tpt and finally its rhs, the commit destructures the duplicated DefDef and then assembles the default getter from the destructured parts instead of doing copyUntyped/copyUntypedInvariant on the original DefDef.

I would say the test coverage is pretty good, as I had to figure out 3 or 4 test failures before I got to the stage when everything worked. Iirc it tests pretty exotic stuff like polymorphic default parameters in both second and third parameter lists, so it looks like we're pretty good in this department.

Eugene Burmako
Owner

@retronym @densh This pull request gave me an idea how we can fix our symbol problems in macro expansions, and I wrote it down here. Afterwards I created a dedicated topic at scala-internals (https://groups.google.com/forum/#!topic/scala-internals/TtCTPlj_qcQ) and updated this comment to move the discussion there.

Jason Zaugg
Owner

Can you please add your comment above to the first commit message?

src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -1174,7 +1174,10 @@ trait Namers extends MethodSynthesis {
* typechecked, the corresponding param would not yet have the "defaultparam"
* flag.
*/
- private def addDefaultGetters(meth: Symbol, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) {
+ private def addDefaultGetters(meth: Symbol, ddef: DefDef, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) {
+ // having defs here is important to make sure that there's no sneaky tree sharing
+ def rtparams = resetLocalAttrs(ddef.duplicate).asInstanceOf[DefDef].tparams
+ def rvparamss = resetLocalAttrs(ddef.duplicate).asInstanceOf[DefDef].vparamss
Jason Zaugg Owner
retronym added a note

I would find the following a little more palatable:

val ddefReset = resetLocalAttrs(ddef.duplicate)
// having defs here is important to make sure that there's no sneaky tree sharing in methods
// with multiple default parameters
def rtparams = ddefReset.tparams.map(_.duplicate)
def rtparams = ddefReset. vparamss.map(_.duplicate)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/compiler/scala/tools/nsc/typechecker/Namers.scala
((30 lines not shown))
val oflag = if (baseHasDefault) OVERRIDE else 0
val name = nme.defaultGetterName(meth.name, posCounter)
- // Create trees for the defaultGetter. Uses tools from Unapplies.scala
- var deftParams = tparams map copyUntyped[TypeDef]
- val defvParamss = mmap(previous) { p =>
- // in the default getter, remove the default parameter
- val p1 = atPos(p.pos.focus) { ValDef(p.mods &~ DEFAULTPARAM, p.name, p.tpt.duplicate, EmptyTree) }
- UnTyper.traverse(p1)
- p1
- }
+ var defTparams = rtparams
+ val defVparamss = mmap(rvparamss.take(previous.length))(rvp => {
+ copyValDef(rvp)(mods = rvp.mods &~ DEFAULTPARAM, rhs = EmptyTree)
+ })
Jason Zaugg Owner
retronym added a note

s/(x => { ... })/{x => ...}/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jason Zaugg retronym commented on the diff
src/compiler/scala/tools/nsc/typechecker/Namers.scala
((12 lines not shown))
- val defTpt = subst(copyUntyped(vparam.tpt match {
- // default getter for by-name params
- case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
- case t => t
- }))
- val defRhs = copyUntyped(vparam.rhs)
+ val defTpt = // don't mess with tpt's of case copy default getters unless you know better
+ if (meth.isCaseCopy) TypeTree()
+ else {
+ // If the parameter type mentions any type parameter of the method, let the compiler infer the
+ // return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
+ // This is better than always using Wildcard for inferring the result type, for example in
+ // def f(i: Int, m: Int => Int = identity _) = m(i)
+ // if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
+ // TODO: this is a very brittle approach; I sincerely hope that Denys's research into hygiene
+ // will open the doors to a much better way of doing this kind of stuff
Jason Zaugg Owner
retronym added a note

I wonder why we don't just typecheck the default expression in the place where it is defined (ie, in the original method definition), and then transplant it into the RHS of the default getter with the normal routine of substitution of type- and value-parameter symbols. We'll have to create the symbols by hand, of course, but that is the routine practice within the compiler.

We will still need some special handling of the expected type of polymorphic defaults, but that should be the only magic.

Jason Zaugg Owner
retronym added a note

This question doesn't need to be answered before we merge this change, but perhaps you've gathered some insights along the way to help us plot the course for further improvements.

Eugene Burmako Owner
xeno-by added a note

I don't know the exact reason why the rhs is typechecked like that. Maybe because it would be tough to set the contexts correctly, though I'm not sure.

Eugene Burmako Owner
xeno-by added a note

What potential problems might arise from using a symless approach like implemented here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Eugene Burmako
Owner

@retronym addressed the feedback

xeno-by added some commits
Eugene Burmako xeno-by awakens default getter synthesis from the untyper nightmare
Our happy little macro paradise is regularly invaded by resetAllAttrs,
the bane of all macros and typers. It’s so ruthless and devastating that
we’ve been long scheming to hack something really cool and to one day
defeat it.

Today we make the first step towards the happy future. Today we overthrow
the UnTyper, resetAllAttrs’s elder brother that rules in the kingdoms of
GetterLand and CaseClassia, and banish him from the land of getters.
In the name of what’s good and meta, let’s band together and completely
drive him away in a subsequent pull request!

To put it in a nutshell, instead of using untyper on a DefDef to do
default getter synthesis, the commit duplicates the DefDef and does
resetLocalAttrs on it.

As default getter synthesis proceeds with figuring out type and value
parameters for the getter, then its tpt and finally its rhs, the commit
destructures the duplicated DefDef and then assembles the default getter
from the destructured parts instead of doing copyUntyped/copyUntypedInvariant
on the original DefDef.

I would say the test coverage is pretty good, as I had to figure out
3 or 4 test failures before I got to the stage when everything worked.
Iirc it tests pretty exotic stuff like polymorphic default parameters
in both second and third parameter lists, so it looks like we're pretty
good in this department.
59cdd50
Eugene Burmako xeno-by untyper is no more
Unlike with default getters, removing untyper from case class synthesis
was trivial. Just resetLocalAttrs on a duplicate of the provided class def,
and that’s it.

resetAllAttrs, you’re next. We’ll get to you! Eventually...
5f08c78
Eugene Burmako xeno-by referenced this pull request from a commit in xeno-by/scala
Eugene Burmako xeno-by ExistentialTypeTree.whereClauses are now MemberDefs
Today’s flight back to Lausanne wasn’t as productive as the recent flight
to Minsk (scala#3305), but I noticed
one minor thingie: ExistentialTypeTree had an imprecise type specified
for its whereClauses. This is now fixed.

I didn’t increment PickleFormat.*Version numbers, because this change
introduces only a miniscule incompatibility with what would have been
a meaningless and most likely crash-inducing pickle anyway.
6c7c3bd
Eugene Burmako xeno-by referenced this pull request from a commit in xeno-by/scala
Eugene Burmako xeno-by ExistentialTypeTree.whereClauses are now MemberDefs
Today’s flight back to Lausanne wasn’t as productive as the recent flight
to Minsk (scala#3305), but I noticed
one minor thingie: ExistentialTypeTree had an imprecise type specified
for its whereClauses. This is now fixed.

I didn’t increment PickleFormat.*Version numbers, because this change
introduces only a miniscule incompatibility with what would have been
a meaningless and most likely crash-inducing pickle anyway.
02a9786
Eugene Burmako xeno-by referenced this pull request from a commit in xeno-by/scala
Eugene Burmako xeno-by ExistentialTypeTree.whereClauses are now MemberDefs
Today’s flight back to Lausanne wasn’t as productive as the recent flight
to Minsk (scala#3305), but I noticed
one minor thingie: ExistentialTypeTree had an imprecise type specified
for its whereClauses. This is now fixed.

I didn’t increment PickleFormat.*Version numbers, because this change
introduces only a miniscule incompatibility with what would have been
a meaningless and most likely crash-inducing pickle anyway.
db6e306
Jason Zaugg
Owner

LGTM.

Jason Zaugg retronym merged commit cafeb34 into from
Eugene Burmako xeno-by deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 7, 2014
  1. Eugene Burmako

    awakens default getter synthesis from the untyper nightmare

    xeno-by authored
    Our happy little macro paradise is regularly invaded by resetAllAttrs,
    the bane of all macros and typers. It’s so ruthless and devastating that
    we’ve been long scheming to hack something really cool and to one day
    defeat it.
    
    Today we make the first step towards the happy future. Today we overthrow
    the UnTyper, resetAllAttrs’s elder brother that rules in the kingdoms of
    GetterLand and CaseClassia, and banish him from the land of getters.
    In the name of what’s good and meta, let’s band together and completely
    drive him away in a subsequent pull request!
    
    To put it in a nutshell, instead of using untyper on a DefDef to do
    default getter synthesis, the commit duplicates the DefDef and does
    resetLocalAttrs on it.
    
    As default getter synthesis proceeds with figuring out type and value
    parameters for the getter, then its tpt and finally its rhs, the commit
    destructures the duplicated DefDef and then assembles the default getter
    from the destructured parts instead of doing copyUntyped/copyUntypedInvariant
    on the original DefDef.
    
    I would say the test coverage is pretty good, as I had to figure out
    3 or 4 test failures before I got to the stage when everything worked.
    Iirc it tests pretty exotic stuff like polymorphic default parameters
    in both second and third parameter lists, so it looks like we're pretty
    good in this department.
  2. Eugene Burmako

    untyper is no more

    xeno-by authored
    Unlike with default getters, removing untyper from case class synthesis
    was trivial. Just resetLocalAttrs on a duplicate of the provided class def,
    and that’s it.
    
    resetAllAttrs, you’re next. We’ll get to you! Eventually...
This page is out of date. Refresh to see the latest.
82 src/compiler/scala/tools/nsc/typechecker/Namers.scala
View
@@ -1132,7 +1132,7 @@ trait Namers extends MethodSynthesis {
}
}
- addDefaultGetters(meth, vparamss, tparams, overriddenSymbol(methResTp))
+ addDefaultGetters(meth, ddef, vparamss, tparams, overriddenSymbol(methResTp))
// fast track macros, i.e. macros defined inside the compiler, are hardcoded
// hence we make use of that and let them have whatever right-hand side they need
@@ -1174,7 +1174,12 @@ trait Namers extends MethodSynthesis {
* typechecked, the corresponding param would not yet have the "defaultparam"
* flag.
*/
- private def addDefaultGetters(meth: Symbol, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) {
+ private def addDefaultGetters(meth: Symbol, ddef: DefDef, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) {
+ val DefDef(_, _, rtparams0, rvparamss0, _, _) = resetLocalAttrs(ddef.duplicate)
+ // having defs here is important to make sure that there's no sneaky tree sharing
+ // in methods with multiple default parameters
+ def rtparams = rtparams0.map(_.duplicate)
+ def rvparamss = rvparamss0.map(_.map(_.duplicate))
val methOwner = meth.owner
val isConstr = meth.isConstructor
val overridden = if (isConstr || !methOwner.isClass) NoSymbol else overriddenSymbol
@@ -1206,23 +1211,36 @@ trait Namers extends MethodSynthesis {
//
vparamss.foldLeft(Nil: List[List[ValDef]]) { (previous, vparams) =>
assert(!overrides || vparams.length == baseParamss.head.length, ""+ meth.fullName + ", "+ overridden.fullName)
+ val rvparams = rvparamss(previous.length)
var baseParams = if (overrides) baseParamss.head else Nil
- for (vparam <- vparams) {
+ map2(vparams, rvparams)((vparam, rvparam) => {
val sym = vparam.symbol
// true if the corresponding parameter of the base class has a default argument
val baseHasDefault = overrides && baseParams.head.hasDefault
if (sym.hasDefault) {
- // generate a default getter for that argument
+ // Create a "default getter", i.e. a DefDef that will calculate vparam.rhs
+ // for those who are going to call meth without providing an argument corresponding to vparam.
+ // After the getter is created, a corresponding synthetic symbol is created and entered into the parent namer.
+ //
+ // In the ideal world, this DefDef would be a simple one-liner that just returns vparam.rhs,
+ // but in scalac things are complicated in two different ways.
+ //
+ // 1) Because the underlying language is quite sophisticated, we must allow for those sophistications in our getter.
+ // Namely: a) our getter has to copy type parameters from the associated method (or the associated class
+ // if meth is a constructor), because vparam.rhs might refer to one of them, b) our getter has to copy
+ // preceding value parameter lists from the associated method, because again vparam.rhs might refer to one of them.
+ //
+ // 2) Because we have already assigned symbols to type and value parameters that we have to copy, we must jump through
+ // hoops in order to destroy them and allow subsequent naming create new symbols for our getter. Previously this
+ // was done in an overly brutal way akin to resetAllAttrs, but now we utilize a resetLocalAttrs-based approach.
+ // Still far from ideal, but at least enables things like run/macro-default-params that were previously impossible.
+
val oflag = if (baseHasDefault) OVERRIDE else 0
val name = nme.defaultGetterName(meth.name, posCounter)
- // Create trees for the defaultGetter. Uses tools from Unapplies.scala
- var deftParams = tparams map copyUntyped[TypeDef]
- val defvParamss = mmap(previous) { p =>
- // in the default getter, remove the default parameter
- val p1 = atPos(p.pos.focus) { ValDef(p.mods &~ DEFAULTPARAM, p.name, p.tpt.duplicate, EmptyTree) }
- UnTyper.traverse(p1)
- p1
+ var defTparams = rtparams
+ val defVparamss = mmap(rvparamss.take(previous.length)){ rvp =>
+ copyValDef(rvp)(mods = rvp.mods &~ DEFAULTPARAM, rhs = EmptyTree)
}
val parentNamer = if (isConstr) {
@@ -1244,7 +1262,8 @@ trait Namers extends MethodSynthesis {
return // fix #3649 (prevent crash in erroneous source code)
}
}
- deftParams = cdef.tparams map copyUntypedInvariant
+ val ClassDef(_, _, rtparams, _) = resetLocalAttrs(cdef.duplicate)
+ defTparams = rtparams.map(rt => copyTypeDef(rt)(mods = rt.mods &~ (COVARIANT | CONTRAVARIANT)))
nmr
}
else ownerNamer getOrElse {
@@ -1255,23 +1274,30 @@ trait Namers extends MethodSynthesis {
nmr
}
- // If the parameter type mentions any type parameter of the method, let the compiler infer the
- // return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
- // This is better than always using Wildcard for inferring the result type, for example in
- // def f(i: Int, m: Int => Int = identity _) = m(i)
- // if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
- val names = deftParams map { case TypeDef(_, name, _, _) => name }
- val subst = new TypeTreeSubstituter(names contains _)
-
- val defTpt = subst(copyUntyped(vparam.tpt match {
- // default getter for by-name params
- case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
- case t => t
- }))
- val defRhs = copyUntyped(vparam.rhs)
+ val defTpt =
+ // don't mess with tpt's of case copy default getters, because assigning something other than TypeTree()
+ // will break the carefully orchestrated naming/typing logic that involves enterCopyMethod and caseClassCopyMeth
+ if (meth.isCaseCopy) TypeTree()
+ else {
+ // If the parameter type mentions any type parameter of the method, let the compiler infer the
+ // return type of the default getter => allow "def foo[T](x: T = 1)" to compile.
+ // This is better than always using Wildcard for inferring the result type, for example in
+ // def f(i: Int, m: Int => Int = identity _) = m(i)
+ // if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable.
+ // TODO: this is a very brittle approach; I sincerely hope that Denys's research into hygiene
+ // will open the doors to a much better way of doing this kind of stuff
Jason Zaugg Owner
retronym added a note

I wonder why we don't just typecheck the default expression in the place where it is defined (ie, in the original method definition), and then transplant it into the RHS of the default getter with the normal routine of substitution of type- and value-parameter symbols. We'll have to create the symbols by hand, of course, but that is the routine practice within the compiler.

We will still need some special handling of the expected type of polymorphic defaults, but that should be the only magic.

Jason Zaugg Owner
retronym added a note

This question doesn't need to be answered before we merge this change, but perhaps you've gathered some insights along the way to help us plot the course for further improvements.

Eugene Burmako Owner
xeno-by added a note

I don't know the exact reason why the rhs is typechecked like that. Maybe because it would be tough to set the contexts correctly, though I'm not sure.

Eugene Burmako Owner
xeno-by added a note

What potential problems might arise from using a symless approach like implemented here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ val tparamNames = defTparams map { case TypeDef(_, name, _, _) => name }
+ val eraseAllMentionsOfTparams = new TypeTreeSubstituter(tparamNames contains _)
+ eraseAllMentionsOfTparams(rvparam.tpt match {
+ // default getter for by-name params
+ case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg
+ case t => t
+ })
+ }
+ val defRhs = rvparam.rhs
val defaultTree = atPos(vparam.pos.focus) {
- DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, deftParams, defvParamss, defTpt, defRhs)
+ DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, defTparams, defVparamss, defTpt, defRhs)
}
if (!isConstr)
methOwner.resetFlag(INTERFACE) // there's a concrete member now
@@ -1286,7 +1312,7 @@ trait Namers extends MethodSynthesis {
}
posCounter += 1
if (overrides) baseParams = baseParams.tail
- }
+ })
if (overrides) baseParamss = baseParamss.tail
previous :+ vparams
}
10 src/compiler/scala/tools/nsc/typechecker/Typers.scala
View
@@ -56,16 +56,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
resetDocComments()
}
- object UnTyper extends Traverser {
- override def traverse(tree: Tree) = {
- if (tree.canHaveAttrs) {
- tree.clearType()
- if (tree.hasSymbolField) tree.symbol = NoSymbol
- }
- super.traverse(tree)
- }
- }
-
sealed abstract class SilentResult[+T] {
@inline final def fold[U](none: => U)(f: T => U): U = this match {
case SilentResultValue(value) => f(value)
23 src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
View
@@ -43,12 +43,6 @@ trait Unapplies extends ast.TreeDSL {
def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption
}
- def copyUntyped[T <: Tree](tree: T): T =
- returning[T](tree.duplicate)(UnTyper traverse _)
-
- def copyUntypedInvariant(td: TypeDef): TypeDef =
- copyTypeDef(copyUntyped(td))(mods = td.mods &~ (COVARIANT | CONTRAVARIANT))
-
private def toIdent(x: DefTree) = Ident(x.name) setPos x.pos.focus
private def classType(cdef: ClassDef, tparams: List[TypeDef]): Tree = {
@@ -58,8 +52,15 @@ trait Unapplies extends ast.TreeDSL {
}
private def constrParamss(cdef: ClassDef): List[List[ValDef]] = {
- val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor cdef.impl.body
- mmap(vparamss)(copyUntyped[ValDef])
+ val ClassDef(_, _, _, Template(_, _, body)) = resetLocalAttrs(cdef.duplicate)
+ val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor body
+ vparamss
+ }
+
+ private def constrTparamsInvariant(cdef: ClassDef): List[TypeDef] = {
+ val ClassDef(_, _, tparams, _) = resetLocalAttrs(cdef.duplicate)
+ val tparamsInvariant = tparams.map(tparam => copyTypeDef(tparam)(mods = tparam.mods &~ (COVARIANT | CONTRAVARIANT)))
+ tparamsInvariant
}
/** The return value of an unapply method of a case class C[Ts]
@@ -125,7 +126,7 @@ trait Unapplies extends ast.TreeDSL {
/** The apply method corresponding to a case class
*/
def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = {
- val tparams = cdef.tparams map copyUntypedInvariant
+ val tparams = constrTparamsInvariant(cdef)
val cparamss = constrParamss(cdef)
def classtpe = classType(cdef, tparams)
atPos(cdef.pos.focus)(
@@ -141,7 +142,7 @@ trait Unapplies extends ast.TreeDSL {
/** The unapply method corresponding to a case class
*/
def caseModuleUnapplyMeth(cdef: ClassDef): DefDef = {
- val tparams = cdef.tparams map copyUntypedInvariant
+ val tparams = constrTparamsInvariant(cdef)
val method = constrParamss(cdef) match {
case xs :: _ if xs.nonEmpty && isRepeatedParamType(xs.last.tpt) => nme.unapplySeq
case _ => nme.unapply
@@ -196,7 +197,7 @@ trait Unapplies extends ast.TreeDSL {
treeCopy.ValDef(vd, Modifiers(flags), vd.name, tpt, rhs)
}
- val tparams = cdef.tparams map copyUntypedInvariant
+ val tparams = constrTparamsInvariant(cdef)
val paramss = classParamss match {
case Nil => Nil
case ps :: pss =>
5 src/reflect/scala/reflect/internal/Symbols.scala
View
@@ -801,9 +801,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
isConstructor && !isPrimaryConstructor
/** Is this symbol a synthetic apply or unapply method in a companion object of a case class? */
+ // xeno-by: why this obscure use of the CASE flag? why not simply compare name with nme.apply and nme.unapply?
Jason Zaugg Owner

Case companions can also have user defined apply methods in addition to the synthetic one.

Eugene Burmako Owner
xeno-by added a note

But user-defined apply methods don't have the SYNTHETIC flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
final def isCaseApplyOrUnapply =
isMethod && isCase && isSynthetic
+ /** Is this symbol a synthetic copy method in a case class? */
+ final def isCaseCopy =
+ isMethod && owner.isCase && isSynthetic && name == nme.copy
+
/** Is this symbol a trait which needs an implementation class? */
final def needsImplClass = (
isTrait
1  test/files/run/macro-default-params.check
View
@@ -0,0 +1 @@
+0
27 test/files/run/macro-default-params/Macros_1.scala
View
@@ -0,0 +1,27 @@
+import scala.language.experimental.macros
+import scala.reflect.macros.WhiteboxContext
+
+object Macros {
+ def id[A]: A = null.asInstanceOf[A]
+
+ def foo: Any = macro impl
+ def impl(c: WhiteboxContext): c.Tree = {
+ import c.universe._
+ import Flag._
+
+ lazy val tpe = TypeTree(typeOf[Int])
+
+ /* If we used this line instead, it would work! */
+ // lazy val tpe = tq"Int"
+
+ lazy val param: ValDef = {
+ val p1 = q"val a: ${tpe.duplicate} = Macros.id[${tpe.duplicate}]"
+ ValDef(Modifiers(DEFAULTPARAM), p1.name, p1.tpt, p1.rhs)
+ }
+
+ q"""
+ class C { def f($param) = a }
+ println(new C().f())
+ """
+ }
+}
3  test/files/run/macro-default-params/Test_2.scala
View
@@ -0,0 +1,3 @@
+object Test extends App {
+ Macros.foo
+}
Something went wrong with that request. Please try again.