diff options
author | Paul Phillips <paulp@improving.org> | 2011-09-26 23:49:03 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-09-26 23:49:03 +0000 |
commit | e412524fee20da975954c929893e88db17dbdd9a (patch) | |
tree | e7c491b8d303879b49426d98f714d81c672d885f /src | |
parent | 660d80f682317eaf3d55a9b63bb95396ac417cdd (diff) | |
download | scala-e412524fee20da975954c929893e88db17dbdd9a.tar.gz scala-e412524fee20da975954c929893e88db17dbdd9a.tar.bz2 scala-e412524fee20da975954c929893e88db17dbdd9a.zip |
ProductN, and method synthesis toolbox.
- Finished giving case classes a ProductN parent, and flipped it on. The
"finish" part involved not breaking existing code where case classes
manually extend the appropriate ProductN. (Like, Tuple 1-22.)
- Generalized most of SyntheticMethods to ease method creation and class
manipulation in general.
- Fixed bugs related to the above, like the fact that this used to be a
compile error:
scala> case class Foo() extends Serializable
<console>:28: error: trait Serializable is inherited twice
case class Foo() extends Serializable
^
It feels like there's a better way to eliminate the duplicate parents,
but after spending a lot of time chasing my tail in that peril-fraught
zone between namer and typer, I don't see an easy path to improve on
it. Closes SI-1799. For that modification to Typers, review by odersky.
Diffstat (limited to 'src')
7 files changed, 332 insertions, 233 deletions
diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala index 2c51ca0db5..17dc33278d 100644 --- a/src/compiler/scala/reflect/internal/Definitions.scala +++ b/src/compiler/scala/reflect/internal/Definitions.scala @@ -420,6 +420,7 @@ trait Definitions extends reflect.api.StandardDefinitions { lazy val ProductClass = mkArityArray("Product", MaxProductArity) lazy val FunctionClass = mkArityArray("Function", MaxFunctionArity, 0) lazy val AbstractFunctionClass = mkArityArray("runtime.AbstractFunction", MaxFunctionArity, 0) + lazy val isProductNClass = ProductClass.toSet def tupleField(n: Int, j: Int) = getMember(TupleClass(n), "_" + j) def isTupleType(tp: Type): Boolean = isTupleType(tp, false) @@ -454,11 +455,7 @@ trait Definitions extends reflect.api.StandardDefinitions { def productProj(n: Int, j: Int): Symbol = productProj(ProductClass(n), j) /** returns true if this type is exactly ProductN[T1,...,Tn], not some subclass */ - def isExactProductType(tp: Type): Boolean = cond(tp.normalize) { - case TypeRef(_, sym, elems) => - val len = elems.length - len <= MaxProductArity && sym == ProductClass(len) - } + def isExactProductType(tp: Type): Boolean = isProductNClass(tp.typeSymbol) def productType(elems: List[Type]) = { if (elems.isEmpty) UnitClass.tpe @@ -472,15 +469,16 @@ trait Definitions extends reflect.api.StandardDefinitions { } } - /** if tpe <: ProductN[T1,...,TN], returns Some((T1,...,TN)) else None */ - def getProductArgs(tpe: Type): Option[List[Type]] = - tpe.baseClasses collectFirst { case x if isExactProductType(x.tpe) => tpe.baseType(x).typeArgs } + /** if tpe <: ProductN[T1,...,TN], returns List(T1,...,TN) else Nil */ + def getProductArgs(tpe: Type): List[Type] = tpe.baseClasses find isProductNClass match { + case Some(x) => tpe.baseType(x).typeArgs + case _ => Nil + } - def unapplyUnwrap(tpe:Type) = (tpe match { - case PolyType(_,MethodType(_, res)) => res - case MethodType(_, res) => res - case tpe => tpe - }).normalize + def unapplyUnwrap(tpe:Type) = tpe.finalResultType.normalize match { + case RefinedType(p :: _, _) => p.normalize + case tp => tp + } def functionApply(n: Int) = getMember(FunctionClass(n), nme.apply) def functionType(formals: List[Type], restpe: Type) = { @@ -753,6 +751,9 @@ trait Definitions extends reflect.api.StandardDefinitions { /** Is symbol a phantom class for which no runtime representation exists? */ lazy val isPhantomClass = Set[Symbol](AnyClass, AnyValClass, NullClass, NothingClass) + /** Is the symbol that of a parent which is added during parsing? */ + lazy val isPossibleSyntheticParent = ProductClass.toSet[Symbol] + ProductRootClass + SerializableClass + private lazy val scalaValueClassesSet = ScalaValueClasses.toSet private lazy val boxedValueClassesSet = boxedClass.values.toSet + BoxedUnitClass diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 17688d42d8..615efb986a 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2675,7 +2675,7 @@ self => def templateOpt(mods: Modifiers, name: Name, constrMods: Modifiers, vparamss: List[List[ValDef]], tstart: Int): Template = { /** A synthetic ProductN parent for case classes. */ def extraCaseParents = ( - if (settings.Xexperimental.value && mods.isCase) { + if (mods.isCase) { val arity = if (vparamss.isEmpty || vparamss.head.isEmpty) 0 else vparamss.head.size if (arity == 0) Nil else List( diff --git a/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala b/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala index dc3328de70..197839c7e0 100644 --- a/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala +++ b/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala @@ -24,7 +24,7 @@ trait ParallelMatching extends ast.TreeDSL self: ExplicitOuter => import global.{ typer => _, _ } - import definitions.{ AnyRefClass, NothingClass, IntClass, BooleanClass, SomeClass, getProductArgs, productProj } + import definitions.{ AnyRefClass, NothingClass, IntClass, BooleanClass, SomeClass, OptionClass, getProductArgs, productProj } import CODE._ import Types._ import Debug._ @@ -355,24 +355,26 @@ trait ParallelMatching extends ast.TreeDSL lazy val unapplyResult: PatternVar = scrut.createVar(unMethod.tpe, Apply(unTarget, scrut.id :: trailing) setType _.tpe) - lazy val cond: Tree = - if (unapplyResult.tpe.isBoolean) unapplyResult.ident - else if (unapplyResult.tpe.typeSymbol == SomeClass) TRUE - else NOT(unapplyResult.ident DOT nme.isEmpty) + lazy val cond: Tree = unapplyResult.tpe.normalize match { + case TypeRef(_, BooleanClass, _) => unapplyResult.ident + case TypeRef(_, SomeClass, _) => TRUE + case _ => NOT(unapplyResult.ident DOT nme.isEmpty) + } lazy val failure = mkFail(zipped.tail filterNot (x => SameUnapplyPattern(x._1)) map { case (pat, r) => r insert pat }) private def doSuccess: (List[PatternVar], List[PatternVar], List[Row]) = { // pattern variable for the unapply result of Some(x).get - lazy val pv = scrut.createVar( - unMethod.tpe typeArgs 0, - _ => fn(ID(unapplyResult.lhs), nme.get) - ) + def unMethodTypeArg = unMethod.tpe.baseType(OptionClass).typeArgs match { + case Nil => log("No type argument for unapply result! " + unMethod.tpe) ; NoType + case arg :: _ => arg + } + lazy val pv = scrut.createVar(unMethodTypeArg, _ => fn(ID(unapplyResult.lhs), nme.get)) def tuple = pv.lhs // at this point it's Some[T1,T2...] - lazy val tpes = getProductArgs(tuple.tpe).get + lazy val tpes = getProductArgs(tuple.tpe) // one pattern variable per tuple element lazy val tuplePVs = diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index d4565c8e0c..89ec052556 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -287,6 +287,8 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R infoStringWithLocation(other), infoStringWithLocation(member) ) + else if (settings.debug.value) + analyzer.foundReqMsg(member.tpe, other.tpe) else "" "overriding %s;\n %s %s%s".format( diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index 9a8bcca2ba..2cc2e2c4aa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -32,132 +32,230 @@ import scala.collection.mutable.ListBuffer trait SyntheticMethods extends ast.TreeDSL { self: Analyzer => - import global._ // the global environment - import definitions._ // standard classes and methods + import global._ + import definitions._ + import CODE._ - /** Add the synthetic methods to case classes. - */ - def addSyntheticMethods(templ: Template, clazz: Symbol, context: Context): Template = { - import CODE._ + private object util { + private type CM[T] = ClassManifest[T] - val localTyper = newTyper( - if (reporter.hasErrors) context makeSilent false else context - ) - def accessorTypes = clazz.caseFieldAccessors map (_.tpe.finalResultType) - def accessorLub = global.weakLub(accessorTypes)._1 + /** To avoid unchecked warnings on polymorphic classes. + */ + def clazzTypeToTest(clazz: Symbol) = clazz.tpe.normalize match { + case TypeRef(_, sym, args) if args.nonEmpty => ExistentialType(sym.typeParams, clazz.tpe) + case tp => tp + } - def hasOverridingImplementation(meth: Symbol): Boolean = { - val sym = clazz.info nonPrivateMember meth.name - def isOverride(s: Symbol) = { - s != meth && !s.isDeferred && !s.isSynthetic && - (clazz.thisType.memberType(s) matches clazz.thisType.memberType(meth)) - } - sym.alternatives exists isOverride + def makeMethodPublic(method: Symbol): Symbol = { + method.privateWithin = NoSymbol + method resetFlag AccessFlags } - def syntheticMethod(name: Name, flags: Int, tpeCons: Symbol => Type) = - newSyntheticMethod(name, flags | OVERRIDE, tpeCons) + def methodArg(method: Symbol, idx: Int): Tree = Ident(method.paramss.head(idx)) + + private def applyTypeInternal(manifests: List[CM[_]]): Type = { + val symbols = manifests map manifestToSymbol + val container :: args = symbols + val tparams = container.typeConstructor.typeParams - def newSyntheticMethod(name: Name, flags: Int, tpeCons: Symbol => Type) = { - val method = clazz.newMethod(clazz.pos.focus, name.toTermName) setFlag flags + // Overly conservative at present - if manifests were more usable + // this could do a lot more. + require(symbols forall (_ ne NoSymbol), "Must find all manifests: " + symbols) + require(container.owner.isPackageClass, "Container must be a top-level class in a package: " + container) + require(tparams.size == args.size, "Arguments must match type constructor arity: " + tparams + ", " + args) + require(args forall (_.typeConstructor.typeParams.isEmpty), "Arguments must be unparameterized: " + args) - clazz.info.decls enter (method setInfo tpeCons(method)) + typeRef(container.typeConstructor.prefix, container, args map (_.tpe)) } - def newTypedMethod(method: Symbol)(body: Tree): Tree = - localTyper typed { DEF(method) === body } + def manifestToSymbol(m: CM[_]): Symbol = m match { + case x: scala.reflect.AnyValManifest[_] => definitions.getClass("scala." + x) + case _ => getClassIfDefined(m.erasure.getName) + } + def companionType[T](implicit m: CM[T]) = + getModule(m.erasure.getName).tpe + + // Use these like `applyType[List, Int]` or `applyType[Map, Int, String]` + def applyType[M](implicit m1: CM[M]): Type = + applyTypeInternal(List(m1)) - def makeNoArgConstructor(res: Type) = - (sym: Symbol) => MethodType(Nil, res) - def makeTypeConstructor(args: List[Type], res: Type) = - (sym: Symbol) => MethodType(sym newSyntheticValueParams args, res) - def makeEqualityMethod(name: Name) = - syntheticMethod(name, 0, makeTypeConstructor(List(AnyClass.tpe), BooleanClass.tpe)) + def applyType[M[X1], X1](implicit m1: CM[M[_]], m2: CM[X1]): Type = + applyTypeInternal(List(m1, m2)) - def newNullaryMethod(name: TermName, tpe: Type, body: Tree) = { - val flags = if (clazz.tpe.member(name) != NoSymbol) OVERRIDE else 0 - val method = clazz.newMethod(clazz.pos.focus, name) setFlag flags + def applyType[M[X1, X2], X1, X2](implicit m1: CM[M[_,_]], m2: CM[X1], m3: CM[X2]): Type = + applyTypeInternal(List(m1, m2, m3)) - clazz.info.decls enter (method setInfo NullaryMethodType(tpe)) - newTypedMethod(method)(body) + def applyType[M[X1, X2, X3], X1, X2, X3](implicit m1: CM[M[_,_,_]], m2: CM[X1], m3: CM[X2], m4: CM[X3]): Type = + applyTypeInternal(List(m1, m2, m3, m4)) + } + import util._ + + class MethodSynthesis(val clazz: Symbol, localTyper: Typer) { + private def isOverride(method: Symbol) = + clazzMember(method.name).alternatives exists (sym => (sym != method) && !sym.isDeferred) + + private def setMethodFlags(method: Symbol): Symbol = { + val overrideFlag = if (isOverride(method)) OVERRIDE else 0L + + method setFlag (overrideFlag | SYNTHETIC) resetFlag DEFERRED } - def productPrefixMethod = newNullaryMethod(nme.productPrefix, StringClass.tpe, LIT(clazz.name.decode)) - def productArityMethod(arity: Int) = newNullaryMethod(nme.productArity, IntClass.tpe, LIT(arity)) - def productIteratorMethod = { - val method = getMember(ScalaRunTimeModule, "typedProductIterator") - val iteratorType = typeRef(NoPrefix, IteratorClass, List(accessorLub)) - newNullaryMethod( - nme.productIterator, - iteratorType, - gen.mkMethodCall(method, List(accessorLub), List(This(clazz))) - ) + private def finishMethod(method: Symbol, f: Symbol => Tree): Tree = { + setMethodFlags(method) + clazz.info.decls enter method + logResult("finishMethod")(localTyper typed DefDef(method, f(method))) } - def projectionMethod(accessor: Symbol, num: Int) = { - newNullaryMethod(nme.productAccessorName(num), accessor.tpe.resultType, REF(accessor)) + private def createInternal(name: Name, f: Symbol => Tree, info: Type): Tree = { + val m = clazz.newMethod(clazz.pos.focus, name.toTermName) + m setInfo info + finishMethod(m, f) + } + private def createInternal(name: Name, f: Symbol => Tree, infoFn: Symbol => Type): Tree = { + val m = clazz.newMethod(clazz.pos.focus, name.toTermName) + m setInfo infoFn(m) + finishMethod(m, f) } - /** Common code for productElement and (currently disabled) productElementName - */ - def perElementMethod(accs: List[Symbol], methodName: Name, resType: Type, caseFn: Symbol => Tree): Tree = { - val symToTpe = makeTypeConstructor(List(IntClass.tpe), resType) - val method = syntheticMethod(methodName, 0, symToTpe) - val arg = method ARG 0 - val default = List(DEFAULT ==> THROW(IndexOutOfBoundsExceptionClass, arg)) - val cases = - for ((sym, i) <- accs.zipWithIndex) yield - CASE(LIT(i)) ==> caseFn(sym) - - newTypedMethod(method)(arg MATCH { cases ::: default : _* }) + private def cloneInternal(original: Symbol, f: Symbol => Tree, name: Name): Tree = { + val m = original.cloneSymbol(clazz) setPos clazz.pos.focus + m.name = name + finishMethod(m, f) + } + + private def cloneInternal(original: Symbol, f: Symbol => Tree): Tree = + cloneInternal(original, f, original.name) + + def clazzMember(name: Name) = clazz.info nonPrivateMember name match { + case NoSymbol => log("In " + clazz + ", " + name + " not found: " + clazz.info) ; NoSymbol + case sym => sym } - def productElementMethod(accs: List[Symbol]): Tree = - perElementMethod(accs, nme.productElement, AnyClass.tpe, x => Ident(x)) + def typeInClazz(sym: Symbol) = clazz.thisType memberType sym + + /** Function argument takes the newly created method symbol of + * the same type as `name` in clazz, and returns the tree to be + * added to the template. + */ + def overrideMethod(name: Name)(f: Symbol => Tree): Tree = + overrideMethod(clazzMember(name))(f) + + def overrideMethod(original: Symbol)(f: Symbol => Tree): Tree = + cloneInternal(original, sym => f(sym setFlag OVERRIDE)) + + def deriveMethod(original: Symbol, nameFn: Name => Name)(f: Symbol => Tree): Tree = + cloneInternal(original, f, nameFn(original.name)) - // def productElementNameMethod(accs: List[Symbol]): Tree = - // perElementMethod(accs, nme.productElementName, StringClass.tpe, x => Literal(x.name.toString)) + def createMethod(name: Name, paramTypes: List[Type], returnType: Type)(f: Symbol => Tree): Tree = + createInternal(name, f, (m: Symbol) => MethodType(m newSyntheticValueParams paramTypes, returnType)) - def moduleToStringMethod: Tree = { - val method = syntheticMethod(nme.toString_, FINAL, makeNoArgConstructor(StringClass.tpe)) - newTypedMethod(method)(LIT(clazz.name.decode)) + def createMethod(name: Name, returnType: Type)(f: Symbol => Tree): Tree = + createInternal(name, f, NullaryMethodType(returnType)) + + def createMethod(original: Symbol)(f: Symbol => Tree): Tree = + createInternal(original.name, f, original.info) + + def forwardMethod(original: Symbol, newMethod: Symbol)(transformArgs: List[Tree] => List[Tree]): Tree = + createMethod(original)(m => gen.mkMethodCall(newMethod, transformArgs(m.paramss.head map Ident))) + + def createSwitchMethod(name: Name, range: Seq[Int], returnType: Type)(f: Int => Tree) = { + createMethod(name, List(IntClass.tpe), returnType) { m => + val arg0 = methodArg(m, 0) + val default = DEFAULT ==> THROW(IndexOutOfBoundsExceptionClass, arg0) + val cases = range.map(num => CASE(LIT(num)) ==> f(num)).toList :+ default + + Match(arg0, cases) + } } - def moduleHashCodeMethod: Tree = { - val method = syntheticMethod(nme.hashCode_, FINAL, makeNoArgConstructor(IntClass.tpe)) - // The string being used as hashcode basis is also productPrefix. - newTypedMethod(method)(LIT(clazz.name.decode.hashCode)) + + // def foo() = constant + def constantMethod(name: Name, value: Any): Tree = { + val constant = Constant(value) + createMethod(name, Nil, constant.tpe)(_ => Literal(constant)) } + // def foo = constant + def constantNullary(name: Name, value: Any): Tree = { + val constant = Constant(value) + createMethod(name, constant.tpe)(_ => Literal(constant)) + } + } + + /** Add the synthetic methods to case classes. + */ + def addSyntheticMethods(templ: Template, clazz0: Symbol, context: Context): Template = { + if (phase.erasedTypes) + return templ - def forwardingMethod(name: Name, targetName: Name): Tree = { - val target = getMember(ScalaRunTimeModule, targetName) - val paramtypes = target.tpe.paramTypes drop 1 - val method = syntheticMethod(name, 0, makeTypeConstructor(paramtypes, target.tpe.resultType)) + val synthesizer = new MethodSynthesis( + clazz0, + newTyper( if (reporter.hasErrors) context makeSilent false else context ) + ) + import synthesizer._ - newTypedMethod(method)(Apply(REF(target), This(clazz) :: (method ARGNAMES))) + def accessors = clazz.caseFieldAccessors + def arity = accessors.size + + def forwardToRuntime(method: Symbol): Tree = + forwardMethod(method, getMember(ScalaRunTimeModule, "_" + method.name toTermName))(This(clazz) :: _) + + // Any member, including private + def hasConcreteImpl(name: Name) = + clazz.info.member(name).alternatives exists (m => !m.isDeferred && !m.isSynthetic) + + def hasOverridingImplementation(meth: Symbol) = { + val sym = clazz.info nonPrivateMember meth.name + sym.alternatives filterNot (_ eq meth) exists { m0 => + !m0.isDeferred && !m0.isSynthetic && (typeInClazz(m0) matches typeInClazz(meth)) + } } - /** The equality method for case modules: - * def equals(that: Any) = this eq that - */ - def equalsModuleMethod: Tree = { - val method = makeEqualityMethod(nme.equals_) + def productIteratorMethod = { + // If this is ProductN[T1, T2, ...], accessorLub is the lub of T1, T2, ..., . + val accessorLub = ( + if (opt.experimental) + global.weakLub(accessors map (_.tpe.finalResultType))._1 match { + case RefinedType(parents, decls) if !decls.isEmpty => RefinedType(parents, EmptyScope) + case tp => tp + } + else AnyClass.tpe + ) + // !!! Hidden behind -Xexperimental due to bummer type inference bugs. + // Refining from Iterator[Any] leads to types like + // + // Option[Int] { def productIterator: Iterator[String] } + // + // appearing legitimately, but this breaks invariant places + // like Manifests and Arrays which are not robust and infer things + // which they shouldn't. + val iteratorType = typeRef(NoPrefix, IteratorClass, List(accessorLub)) - newTypedMethod(method)(This(clazz) ANY_EQ (method ARG 0)) + createMethod(nme.productIterator, iteratorType)(_ => + gen.mkMethodCall(ScalaRunTimeModule, "typedProductIterator", List(accessorLub), List(This(clazz))) + ) + } + def projectionMethod(accessor: Symbol, num: Int) = { + createMethod(nme.productAccessorName(num), accessor.tpe.resultType)(_ => REF(accessor)) } - /** To avoid unchecked warnings on polymorphic classes. + /** Common code for productElement and (currently disabled) productElementName */ - def clazzTypeToTest = clazz.tpe.normalize match { - case TypeRef(pre, sym, args) if args.nonEmpty => ExistentialType(sym.typeParams, clazz.tpe) - case tp => tp - } + def perElementMethod(name: Name, returnType: Type)(caseFn: Symbol => Tree): Tree = + createSwitchMethod(name, accessors.indices, returnType)(idx => caseFn(accessors(idx))) + + def productElementNameMethod = perElementMethod(nme.productElement, StringClass.tpe)(x => LIT(x.name.toString)) + + /** The equality method for case modules: + * def equals(that: Any) = this eq that + */ + def equalsModuleMethod: Tree = + createMethod(Object_equals)(m => This(clazz) ANY_EQ methodArg(m, 0)) /** The canEqual method for case classes. * def canEqual(that: Any) = that.isInstanceOf[This] */ def canEqualMethod: Tree = { - val method = makeEqualityMethod(nme.canEqual_) - - newTypedMethod(method)((method ARG 0) IS_OBJ clazzTypeToTest) + createMethod(nme.canEqual_, List(AnyClass.tpe), BooleanClass.tpe)(m => + methodArg(m, 0) IS_OBJ clazzTypeToTest(clazz) + ) } /** The equality method for case classes. @@ -171,145 +269,123 @@ trait SyntheticMethods extends ast.TreeDSL { * } * } */ - def equalsClassMethod: Tree = { - val method = makeEqualityMethod(nme.equals_) - val that = Ident(method.paramss.head.head) - val typeTest = gen.mkIsInstanceOf(that, clazzTypeToTest, true, false) - val typeCast = that AS_ATTR clazz.tpe - - def argsBody = { - val valName = context.unit.freshTermName(clazz.name + "$") - val valSym = method.newValue(method.pos, valName) setInfo clazz.tpe setFlag SYNTHETIC - val valDef = ValDef(valSym, typeCast) - val canEqualCheck = gen.mkMethodCall(valSym, nme.canEqual_, Nil, List(This(clazz))) - val pairwise = clazz.caseFieldAccessors map { accessor => - val method = accessor.tpe member nme.EQ - fn(Select(This(clazz), accessor), method, Select(Ident(valSym), accessor)) + def equalsClassMethod: Tree = createMethod(nme.equals_, List(AnyClass.tpe), BooleanClass.tpe) { m => + val arg0 = methodArg(m, 0) + val thatTest = gen.mkIsInstanceOf(arg0, clazzTypeToTest(clazz), true, false) + val thatCast = arg0 AS_ATTR clazz.tpe + + def argsBody: Tree = { + val otherName = context.unit.freshTermName(clazz.name + "$") + val otherSym = m.newValue(m.pos, otherName) setInfo clazz.tpe setFlag SYNTHETIC + val pairwise = accessors map (acc => fn(Select(This(clazz), acc), acc.tpe member nme.EQ, Select(Ident(otherSym), acc))) + val canEq = gen.mkMethodCall(otherSym, nme.canEqual_, Nil, List(This(clazz))) + def block = Block(ValDef(otherSym, thatCast), AND(pairwise :+ canEq: _*)) + + (This(clazz) ANY_EQ arg0) OR { + thatTest AND Block( + ValDef(otherSym, thatCast), + AND(pairwise :+ canEq: _*) + ) } + } + if (accessors.isEmpty) + thatTest AND ((thatCast DOT nme.canEqual_)(This(clazz))) + else + argsBody + } - ( - (This(clazz) ANY_EQ that) OR - (typeTest AND Block(valDef, AND(pairwise :+ canEqualCheck: _*))) - ) + def newAccessorMethod(ddef: DefDef): Tree = { + deriveMethod(ddef.symbol, name => context.unit.freshTermName(name + "$")) { newAcc => + makeMethodPublic(newAcc) + newAcc resetFlag (ACCESSOR | PARAMACCESSOR) + ddef.rhs.duplicate } + } + + // A buffer collecting additional methods for the template body + val ts = new ListBuffer[Tree] - newTypedMethod(method) { - if (clazz.caseFieldAccessors.isEmpty) - typeTest AND ((typeCast DOT nme.canEqual_)(This(clazz))) - else - argsBody + def makeAccessorsPublic() { + // If this case class has fields with less than public visibility, their getter at this + // point also has those permissions. In that case we create a new, public accessor method + // with a new name and remove the CASEACCESSOR flag from the existing getter. This complicates + // the retrieval of the case field accessors (see def caseFieldAccessors in Symbols.) + def needsService(s: Symbol) = s.isMethod && s.isCaseAccessor && !s.isPublic + for (ddef @ DefDef(_, _, _, _, _, _) <- templ.body; if needsService(ddef.symbol)) { + ts += newAccessorMethod(ddef) + ddef.symbol resetFlag CASEACCESSOR } } + /** The _1, _2, etc. methods to implement ProductN. + */ + def productNMethods = { + val accs = accessors.toIndexedSeq + 1 to arity map (num => productProj(arity, num) -> (() => projectionMethod(accs(num - 1), num))) + } - def newAccessorMethod(tree: Tree): Tree = tree match { - case DefDef(_, _, _, _, _, rhs) => - val newAcc = tree.symbol.cloneSymbol - newAcc.name = context.unit.freshTermName(tree.symbol.name + "$") - newAcc setFlag SYNTHETIC resetFlag (ACCESSOR | PARAMACCESSOR | PRIVATE | PROTECTED) - newAcc.privateWithin = NoSymbol - newAcc.owner.info.decls enter newAcc - logResult("new accessor method")(newTypedMethod(newAcc)(rhs.duplicate)) + // methods for both classes and objects + def productMethods = { + List( + Product_productPrefix -> (() => constantNullary(nme.productPrefix, clazz.name.decode)), + Product_productArity -> (() => constantNullary(nme.productArity, arity)), + Product_productElement -> (() => perElementMethod(nme.productElement, AnyClass.tpe)(Ident)), + Product_iterator -> (() => productIteratorMethod), + Product_canEqual -> (() => canEqualMethod) + // This is disabled pending a reimplementation which doesn't add any + // weight to case classes (i.e. inspects the bytecode.) + // Product_productElementName -> (() => productElementNameMethod(accessors)), + ) } - def needsReadResolve = ( - // Only nested objects inside objects should get readResolve automatically. - // Otherwise, after de-serialization we get null references for lazy accessors - // (nested object -> lazy val + class def) since the bitmap gets serialized but - // the moduleVar not. - clazz.isSerializable && (clazz.owner.isPackageClass || clazz.owner.isModuleClass) + def caseClassMethods = productMethods ++ productNMethods ++ Seq( + Object_hashCode -> (() => forwardToRuntime(Object_hashCode)), + Object_toString -> (() => forwardToRuntime(Object_toString)), + Object_equals -> (() => equalsClassMethod) ) - // A buffer collecting additional methods for the template body - val ts = new ListBuffer[Tree] + def caseObjectMethods = productMethods ++ Seq( + Object_hashCode -> (() => constantMethod(nme.hashCode_, clazz.name.decode.hashCode)), + Object_toString -> (() => constantMethod(nme.toString_, clazz.name.decode)) + ) - if (!phase.erasedTypes) try { + def synthesize() = { if (clazz.isCase) { - val isTop = clazz.ancestors forall (x => !x.isCase) - val accessors = clazz.caseFieldAccessors - val arity = accessors.size - - if (isTop) { - // If this case class has fields with less than public visibility, their getter at this - // point also has those permissions. In that case we create a new, public accessor method - // with a new name and remove the CASEACCESSOR flag from the existing getter. This complicates - // the retrieval of the case field accessors (see def caseFieldAccessors in Symbols.) - def needsService(s: Symbol) = s.isMethod && s.isCaseAccessor && !s.isPublic - for (stat <- templ.body ; if stat.isDef && needsService(stat.symbol)) { - ts += newAccessorMethod(stat) - stat.symbol resetFlag CASEACCESSOR - } - } - /** The _1, _2, etc. methods to implement ProductN, and an override - * of productIterator with a more specific element type. - * Only enabled under -Xexperimental. - */ - def productNMethods = { - val projectionMethods = (accessors, 1 to arity).zipped map ((accessor, num) => - productProj(arity, num) -> (() => projectionMethod(accessor, num)) - ) - projectionMethods :+ ( - Product_iterator -> (() => productIteratorMethod) - ) - } + makeAccessorsPublic() + val methods = if (clazz.isModuleClass) caseObjectMethods else caseClassMethods - // methods for case classes only - def classMethods = List( - Object_hashCode -> (() => forwardingMethod(nme.hashCode_, "_" + nme.hashCode_)), - Object_toString -> (() => forwardingMethod(nme.toString_, "_" + nme.toString_)), - Object_equals -> (() => equalsClassMethod) - ) ++ ( - if (settings.Xexperimental.value) productNMethods - else Nil - ) - - // methods for case objects only - def objectMethods = List( - Object_hashCode -> (() => moduleHashCodeMethod), - Object_toString -> (() => moduleToStringMethod) - ) - // methods for both classes and objects - def everywhereMethods = { - List( - Product_productPrefix -> (() => productPrefixMethod), - Product_productArity -> (() => productArityMethod(accessors.length)), - Product_productElement -> (() => productElementMethod(accessors)), - // This is disabled pending a reimplementation which doesn't add any - // weight to case classes (i.e. inspects the bytecode.) - // Product_productElementName -> (() => productElementNameMethod(accessors)), - Product_canEqual -> (() => canEqualMethod) - ) - } - - val methods = (if (clazz.isModuleClass) objectMethods else classMethods) ++ everywhereMethods for ((m, impl) <- methods ; if !hasOverridingImplementation(m)) ts += impl() } + /** If you serialize a singleton and then deserialize it twice, + * you will have two instances of your singleton, unless you implement + * the readResolve() method (see http://www.javaworld.com/javaworld/ + * jw-04-2003/jw-0425-designpatterns_p.html) + */ if (clazz.isModuleClass) { - def hasReadResolve = { - val sym = clazz.info member nme.readResolve // any member, including private - sym.isTerm && !sym.isDeferred - } - - /** If you serialize a singleton and then deserialize it twice, - * you will have two instances of your singleton, unless you implement - * the readResolve() method (see http://www.javaworld.com/javaworld/ - * jw-04-2003/jw-0425-designpatterns_p.html) - */ - if (!hasReadResolve && needsReadResolve) { + // Only nested objects inside objects should get readResolve automatically. + // Otherwise, after de-serialization we get null references for lazy accessors + // (nested object -> lazy val + class def) since the bitmap gets serialized but + // the moduleVar not. + def needsReadResolve = + !hasConcreteImpl(nme.readResolve) && clazz.isSerializable && clazz.owner.isModuleClass + + if (needsReadResolve) { // PP: To this day I really can't figure out what this next comment is getting at: // the !!! normally means there is something broken, but if so, what is it? // // !!! the synthetic method "readResolve" should be private, but then it is renamed !!! - val method = newSyntheticMethod(nme.readResolve, PROTECTED, makeNoArgConstructor(ObjectClass.tpe)) - ts += newTypedMethod(method)(REF(clazz.sourceModule)) + ts += createMethod(nme.readResolve, Nil, ObjectClass.tpe) { m => + m setFlag PROTECTED + REF(clazz.sourceModule) + } } } - } catch { - case ex: TypeError => - if (!reporter.hasErrors) throw ex } + try synthesize() + catch { case _: TypeError if reporter.hasErrors => () } + if (phase.id <= currentRun.typerPhase.id) { treeCopy.Template(templ, templ.parents, templ.self, if (ts.isEmpty) templ.body else templ.body ++ ts // avoid copying templ.body if empty diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d1ba2df815..1c8b2486c9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1213,8 +1213,26 @@ trait Typers extends Modes with Adaptations { */ //Console.println("parents("+clazz") = "+supertpt :: mixins);//DEBUG - supertpt :: mixins mapConserve (tpt => checkNoEscaping.privates(clazz, tpt)) - } catch { + + // Certain parents are added in the parser before it is known whether + // that class also declared them as parents. For instance, this is an + // error unless we take corrective action here: + // + // case class Foo() extends Serializable + // + // So we strip the duplicates before typer. + def fixDuplicates(remaining: List[Tree]): List[Tree] = remaining match { + case Nil => Nil + case x :: xs => + val sym = x.symbol + x :: fixDuplicates( + if (isPossibleSyntheticParent(sym)) xs filterNot (_.symbol == sym) + else xs + ) + } + fixDuplicates(supertpt :: mixins) mapConserve (tpt => checkNoEscaping.privates(clazz, tpt)) + } + catch { case ex: TypeError => templ.tpe = null reportTypeError(templ.pos, ex) diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 67dd272d5a..4f7e6225e1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -42,11 +42,11 @@ trait Unapplies extends ast.TreeDSL tp.typeSymbol match { // unapplySeqResultToMethodSig case BooleanClass => Nil case OptionClass | SomeClass => - val prod = tp.typeArgs.head - getProductArgs(prod) match { - case Some(xs) if xs.size > 1 => xs // n > 1 - case _ => List(prod) // special n == 0 || n == 1 - } + val prod = tp.typeArgs.head + val targs = getProductArgs(prod) + + if (targs.isEmpty || targs.tail.isEmpty) List(prod) // special n == 0 || n == 1 + else targs // n > 1 case _ => throw new TypeError("result type "+tp+" of unapply not in {Boolean, Option[_], Some[_]}") } |