From e21d9b0a3907ee59b4d05489ecaf0fbf6467e27f Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 19 Sep 2011 15:48:23 +0000 Subject: Added tool for analyzing forwarder classes. If you build and then run tools/proxy-report you will be given files revealing interesting things like: /** With respect to trait SetLike, trait SetProxyLike does NOT wrap: */ trait Unwrapped { def +(elem1: A,elem2: A,elems: A*): This def ++(elems: scala.collection.GenTraversableOnce[A]): This def subsets(len: Int): Iterator[This] def subsets: Iterator[This] protected override def newBuilder: scala.collection.mutable.Builder[A,This] protected override def parCombiner: scala.collection.parallel.Combiner[A,scala.collection.parallel.ParSet[A]] } Lots more possible here, for now I just want to get a backstop against our worst transgressions (not just in the library -- look at SimpleTypeProxy, if you dare!) I will inquire about the results separately, so no review. --- src/compiler/scala/reflect/internal/Symbols.scala | 3 + .../scala/tools/nsc/typechecker/RefChecks.scala | 56 ++++---- .../scala/tools/nsc/util/ProxyReport.scala | 146 +++++++++++++++++++++ 3 files changed, 179 insertions(+), 26 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/util/ProxyReport.scala (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Symbols.scala b/src/compiler/scala/reflect/internal/Symbols.scala index 9757297e8f..7127e989fb 100644 --- a/src/compiler/scala/reflect/internal/Symbols.scala +++ b/src/compiler/scala/reflect/internal/Symbols.scala @@ -1615,6 +1615,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => else owner.ancestors map overriddenSymbol filter (_ != NoSymbol) /** Equivalent to allOverriddenSymbols.nonEmpty, but more efficient. */ + // !!! When if ever will this answer differ from .isOverride? + // How/where is the OVERRIDE flag managed, as compared to how checks + // based on type membership will evaluate? def isOverridingSymbol = owner.isClass && ( owner.ancestors exists (cls => matchingSymbol(cls, owner.thisType) != NoSymbol) ) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 8828889231..68cc6db2fe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -82,6 +82,28 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R if (sym.hasAccessBoundary) "" + sym.privateWithin.name else "" ) + def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type): Boolean = (tp1.normalize, tp2.normalize) match { + case (MethodType(List(), rtp1), NullaryMethodType(rtp2)) => + rtp1 <:< rtp2 + case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) => + rtp1 <:< rtp2 + case (TypeRef(_, sym, _), _) if sym.isModuleClass => + overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix) + case _ => + def classBoundAsSeen(tp: Type) = tp.typeSymbol.classBound.asSeenFrom(prefix, tp.typeSymbol.owner) + + (tp1 <:< tp2) || ( // object override check + tp1.typeSymbol.isModuleClass && tp2.typeSymbol.isModuleClass && { + val cb1 = classBoundAsSeen(tp1) + val cb2 = classBoundAsSeen(tp2) + (cb1 <:< cb2) && { + log("Allowing %s to override %s because %s <:< %s".format(tp1, tp2, cb1, cb2)) + true + } + } + ) + } + class RefCheckTransformer(unit: CompilationUnit) extends Transformer { var localTyper: analyzer.Typer = typer; @@ -227,17 +249,6 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } } - def isConformingObjectOverride(tp1: Type, tp2: Type) = { - tp1.typeSymbol.isModuleClass && tp2.typeSymbol.isModuleClass && { - val cb1 = classBoundAsSeen(tp1) - val cb2 = classBoundAsSeen(tp2) - - (cb1 <:< cb2) && { - log("Allowing %s to override %s because %s <:< %s".format(tp1, tp2, cb1, cb2)) - true - } - } - } def isAbstractTypeWithoutFBound(sym: Symbol) = // (part of DEVIRTUALIZE) sym.isAbstractType && !sym.isFBounded @@ -256,21 +267,12 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R else "") } - def overridesType(tp1: Type, tp2: Type): Boolean = (tp1.normalize, tp2.normalize) match { - case (MethodType(List(), rtp1), NullaryMethodType(rtp2)) => - rtp1 <:< rtp2 - case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) => - rtp1 <:< rtp2 - case (TypeRef(_, sym, _), _) if sym.isModuleClass => - overridesType(NullaryMethodType(tp1), tp2) - case _ => - (tp1 <:< tp2) || isConformingObjectOverride(tp1, tp2) - } - /** Check that all conditions for overriding `other` by `member` * of class `clazz` are met. */ - def checkOverride(clazz: Symbol, member: Symbol, other: Symbol) { + def checkOverride(member: Symbol, other: Symbol) { + def memberTp = self.memberType(member) + def otherTp = self.memberType(other) def noErrorType = other.tpe != ErrorType && member.tpe != ErrorType def isRootOrNone(sym: Symbol) = sym == RootClass || sym == NoSymbol def objectOverrideErrorMsg = ( @@ -400,6 +402,8 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } } + + def checkOverrideTypes() { if (other.isAliasType) { //if (!member.typeParams.isEmpty) (1.5) @MAT @@ -449,13 +453,13 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R other.cookJavaRawInfo() // #2454 val memberTp = self.memberType(member) val otherTp = self.memberType(other) - if (!overridesType(memberTp, otherTp)) { // 8 + if (!overridesTypeInPrefix(memberTp, otherTp, self)) { // 8 overrideTypeError() explainTypes(memberTp, otherTp) } if (member.isStable && !otherTp.isVolatile) { - if (memberTp.isVolatile) + if (memberTp.isVolatile) overrideError("has a volatile type; cannot override a member with non-volatile type") else memberTp.normalize.resultType match { case rt: RefinedType if !(rt =:= otherTp) && !(checkedCombinations contains rt.parents) => @@ -474,7 +478,7 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R val opc = new overridingPairs.Cursor(clazz) while (opc.hasNext) { //Console.println(opc.overriding/* + ":" + opc.overriding.tpe*/ + " in "+opc.overriding.fullName + " overrides " + opc.overridden/* + ":" + opc.overridden.tpe*/ + " in "+opc.overridden.fullName + "/"+ opc.overridden.hasFlag(DEFERRED));//debug - if (!opc.overridden.isClass) checkOverride(clazz, opc.overriding, opc.overridden); + if (!opc.overridden.isClass) checkOverride(opc.overriding, opc.overridden); opc.next } diff --git a/src/compiler/scala/tools/nsc/util/ProxyReport.scala b/src/compiler/scala/tools/nsc/util/ProxyReport.scala new file mode 100644 index 0000000000..86cf2006bb --- /dev/null +++ b/src/compiler/scala/tools/nsc/util/ProxyReport.scala @@ -0,0 +1,146 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package util + +import scala.collection.{ mutable, immutable, generic } + +/** A class for analyzing forwarding/proxy relationships. + */ +trait ProxyReport { + val global: Global + import global._ + import definitions.{ getClass => gc, _ } + + private object classes { + def isIgnorable(sym: Symbol) = sym :: sym.allOverriddenSymbols exists { s => + ObjectClass isSubClass s.owner + } + def nonPrivateMethods(sym: Symbol) = { + val methods = sym.initialize.tpe.nonPrivateMembers filter { x => + x.isMethod && !x.isConstructor && !x.isPrivate && !isIgnorable(x) + } + methods foreach (m => m.initialize.info.paramss.flatten foreach (_.initialize)) + methods + } + lazy val GlobalClass = gc(classOf[Global].getName) + lazy val GenericClass = getModule("scala.collection.generic").moduleClass + lazy val CollectionClass = getModule("scala.collection").moduleClass + + def getType(name: String) = getMember(GlobalClass, name.toTypeName) + def getColl(name: String) = getMember(CollectionClass, name.toTypeName) + def getGeneric(name: String) = getMember(GenericClass, name.toTypeName) + + // the following operations + those in RewrappingTypeProxy are all operations + // in class Type that are overridden in some subclass + // Important to keep this up-to-date when new operations are added! + def TypeClass = getType("Type") + def SimpleTypeProxy = getType("SimpleTypeProxy") + def RewrappingTypeProxy = getType("RewrappingTypeProxy") + + def TraversableForwarder = getGeneric("TraversableForwarder") + def IterableForwarder = getGeneric("IterableForwarder") + def SeqForwarder = getGeneric("SeqForwarder") + def TraversableLike = getColl("TraversableLike") + def TraversableProxy = getColl("TraversableProxyLike") + def IterableLike = getColl("IterableLike") + def IterableProxy = getColl("IterableProxyLike") + def MapLike = getColl("MapLike") + def MapProxy = getColl("MapProxyLike") + def SeqLike = getColl("SeqLike") + def SeqProxy = getColl("SeqProxyLike") + def SetLike = getColl("SetLike") + def SetProxy = getColl("SetProxyLike") + } + import classes._ + + val wrappedHeader = """ +/** With respect to %s, %s wraps: + */ +trait Wrapped { + """.trim + val unwrappedHeader = """ +/** With respect to %s, %s does NOT wrap: + */ +trait Unwrapped { + """.trim + + def wrapReport(underlying: Symbol, proxy: Symbol) = { + val underlyingMs = nonPrivateMethods(underlying) + val proxyMs = nonPrivateMethods(proxy) filterNot (_.owner == underlying) + val (wrapped, unwrapped) = underlyingMs partition (m => + proxyMs exists (p => + (p.name == m.name) && { + val self = proxy.thisType + val memberTp = self.memberType(p) + val parentTp = self.memberType(m) + + refChecks.overridesTypeInPrefix(memberTp, parentTp, self) + // || { + // // if (p.paramss.flatten.length == m.paramss.flatten.length) + // // println("names equal, overridesType false:\n " + ((p, m, memberTp, parentTp, self)) + "\n") + // + // false + // } + } + ) + ) + + def show(xs: List[Symbol], template: String) = { + val lines = xs.map(_.initialize.defString).sorted.map(" " + _ + "\n") + lines.mkString(template.format(underlying, proxy) + "\n", "", "}") + } + + show(wrapped, wrappedHeader) + "\n\n" + show(unwrapped, unwrappedHeader) + } + + lazy val wrappers = List( + TypeClass -> SimpleTypeProxy, + TypeClass -> RewrappingTypeProxy, + TraversableClass -> TraversableForwarder, + IterableClass -> IterableForwarder, + SeqClass -> SeqForwarder, + TraversableLike -> TraversableProxy, + IterableLike -> IterableProxy, + MapLike -> MapProxy, + SetLike -> SetProxy, + SeqLike -> SeqProxy + ) + + def generate(dir: io.Directory) = { + /** A proxy for a type (identified by field `underlying`) that forwards most + * operations to it (for exceptions, see WrappingProxy, which forwards even more operations). + * every operation that is overridden for some kind of types should be forwarded. + */ + for ((clazz, proxy) <- wrappers) { + val text = wrapReport(clazz, proxy) + val file = dir / (proxy.fullName + ".scala") toFile; + + file writeAll text + println("Created " + file) + } + } +} + +object ProxyReportRunner { + class ProxyGlobal(s: Settings) extends Global(s) { + object proxyReport extends { + val global: ProxyGlobal.this.type = ProxyGlobal.this + } with util.ProxyReport + } + + def main(args: Array[String]): Unit = { + if (args.isEmpty) + return println("Give an output directory as argument.") + + val dir = io.Directory(args(0)).createDirectory() + val s = new Settings() + s.processArguments(args.toList.tail, true) + val g = new ProxyGlobal(s) + val run = new g.Run() + g.atPhase(run.typerPhase.next)(g.proxyReport.generate(dir)) + } +} -- cgit v1.2.3