diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-07-29 10:59:44 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-07-29 10:59:44 -0700 |
commit | 59e21f37cb80215ada0db60363fb9f30adf6a0cd (patch) | |
tree | 15bcc34aca26fe97aafd447fcec0df3f2a5e51c4 /src/reflect | |
parent | 20cd9474f0a22950c905badb81fb6eeebdf00b34 (diff) | |
parent | 4d6be05c28c95dcd26922059d773a8bfed6014ef (diff) | |
download | scala-59e21f37cb80215ada0db60363fb9f30adf6a0cd.tar.gz scala-59e21f37cb80215ada0db60363fb9f30adf6a0cd.tar.bz2 scala-59e21f37cb80215ada0db60363fb9f30adf6a0cd.zip |
Merge pull request #2746 from soc/topic/paulp-typer-debug-output
Improve type printing (toString/debugging)
Diffstat (limited to 'src/reflect')
-rw-r--r-- | src/reflect/scala/reflect/internal/Definitions.scala | 63 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Symbols.scala | 14 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/TypeDebugging.scala | 123 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Types.scala | 75 |
4 files changed, 179 insertions, 96 deletions
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 6a9fa9a884..6b7aa2dddf 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -580,10 +580,11 @@ trait Definitions extends api.StandardDefinitions { } val MaxTupleArity, MaxProductArity, MaxFunctionArity = 22 + lazy val ProductClass: Array[ClassSymbol] = prepend(UnitClass, mkArityArray("Product", MaxProductArity, 1)) - lazy val TupleClass: Array[Symbol] = prepend(NoSymbol, mkArityArray("Tuple", MaxTupleArity, 1)) - lazy val FunctionClass = mkArityArray("Function", MaxFunctionArity, 0) - lazy val AbstractFunctionClass = mkArityArray("runtime.AbstractFunction", MaxFunctionArity, 0) + lazy val TupleClass: Array[Symbol] = prepend(null, mkArityArray("Tuple", MaxTupleArity, 1)) + lazy val FunctionClass = mkArityArray("Function", MaxFunctionArity, 0) + lazy val AbstractFunctionClass = mkArityArray("runtime.AbstractFunction", MaxFunctionArity, 0) /** Creators for TupleN, ProductN, FunctionN. */ def tupleType(elems: List[Type]) = aritySpecificType(TupleClass, elems) @@ -608,6 +609,9 @@ trait Definitions extends api.StandardDefinitions { // NOTE: returns true for NoSymbol since it's included in the TupleClass array -- is this intensional? def isTupleSymbol(sym: Symbol) = TupleClass contains unspecializedSymbol(sym) def isProductNClass(sym: Symbol) = ProductClass contains sym + def tupleField(n: Int, j: Int) = getMemberValue(TupleClass(n), nme.productAccessorName(j)) + def isFunctionSymbol(sym: Symbol) = FunctionClass contains unspecializedSymbol(sym) + def isProductNSymbol(sym: Symbol) = ProductClass contains unspecializedSymbol(sym) def unspecializedSymbol(sym: Symbol): Symbol = { if (sym hasFlag SPECIALIZED) { @@ -618,31 +622,8 @@ trait Definitions extends api.StandardDefinitions { } else sym } - - // Checks whether the given type is true for the given condition, - // or if it is a specialized subtype of a type for which it is true. - // - // Origins notes: - // An issue was introduced with specialization in that the implementation - // of "isTupleType" in Definitions relied upon sym == TupleClass(elems.length). - // This test is untrue for specialized tuples, causing mysterious behavior - // because only some tuples are specialized. - def isPossiblySpecializedType(tp: Type)(cond: Type => Boolean) = { - cond(tp) || (tp match { - case TypeRef(pre, sym, args) if sym hasFlag SPECIALIZED => - cond(tp baseType unspecializedSymbol(sym)) - case _ => - false - }) - } - // No normalization. - def isTupleTypeDirect(tp: Type) = isPossiblySpecializedType(tp) { - case TypeRef(_, sym, args) if args.nonEmpty => - val len = args.length - len <= MaxTupleArity && sym == TupleClass(len) - case _ => false - } - def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) + def unspecializedTypeArgs(tp: Type): List[Type] = + (tp baseType unspecializedSymbol(tp.typeSymbolDirect)).typeArgs def isMacroBundleType(tp: Type) = { val isNonTrivial = tp != ErrorType && tp != NothingTpe && tp != NullTpe @@ -654,6 +635,16 @@ trait Definitions extends api.StandardDefinitions { def isIterableType(tp: Type) = tp <:< classExistentialType(IterableClass) + // These "direct" calls perform no dealiasing. They are most needed when + // printing types when one wants to preserve the true nature of the type. + def isFunctionTypeDirect(tp: Type) = isFunctionSymbol(tp.typeSymbolDirect) + def isTupleTypeDirect(tp: Type) = isTupleSymbol(tp.typeSymbolDirect) + + // Note that these call .dealiasWiden and not .normalize, the latter of which + // tends to change the course of events by forcing types. + def isFunctionType(tp: Type) = isFunctionTypeDirect(tp.dealiasWiden) + def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) + lazy val ProductRootClass: ClassSymbol = requiredClass[scala.Product] def Product_productArity = getMemberMethod(ProductRootClass, nme.productArity) def Product_productElement = getMemberMethod(ProductRootClass, nme.productElement) @@ -662,9 +653,13 @@ trait Definitions extends api.StandardDefinitions { def Product_canEqual = getMemberMethod(ProductRootClass, nme.canEqual_) def productProj(z:Symbol, j: Int): TermSymbol = getMemberValue(z, nme.productAccessorName(j)) + def productProj(n: Int, j: Int): TermSymbol = productProj(ProductClass(n), j) + + /** returns true if this type is exactly ProductN[T1,...,Tn], not some subclass */ + def isExactProductType(tp: Type): Boolean = isProductNSymbol(tp.typeSymbol) /** if tpe <: ProductN[T1,...,TN], returns List(T1,...,TN) else Nil */ - def getProductArgs(tpe: Type): List[Type] = tpe.baseClasses find isProductNClass match { + def getProductArgs(tpe: Type): List[Type] = tpe.baseClasses find isProductNSymbol match { case Some(x) => tpe.baseType(x).typeArgs case _ => Nil } @@ -683,13 +678,9 @@ trait Definitions extends api.StandardDefinitions { assert(isFunctionType(tp), tp) abstractFunctionType(tp.typeArgs.init, tp.typeArgs.last) } - - def isFunctionType(tp: Type): Boolean = tp.dealiasWiden match { - case TypeRef(_, sym, args) if args.nonEmpty => - val arity = args.length - 1 // -1 is the return type - arity <= MaxFunctionArity && sym == FunctionClass(arity) - case _ => - false + def functionNBaseType(tp: Type): Type = tp.baseClasses find isFunctionSymbol match { + case Some(sym) => tp baseType unspecializedSymbol(sym) + case _ => tp } def isPartialFunctionType(tp: Type): Boolean = { diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 424296c212..e41038cafc 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -3152,6 +3152,20 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def children = childSet override def addChild(sym: Symbol) { childSet = childSet + sym } + def anonOrRefinementString = { + if (hasCompleteInfo) { + val label = if (isAnonymousClass) "$anon:" else "refinement of" + val parents = parentsString(info.parents map functionNBaseType filterNot (_.typeSymbol == SerializableClass)) + s"<$label $parents>" + } + else if (isAnonymousClass) "$anon" + else nameString + } + override def toString = ( + if (isAnonOrRefinementClass) anonOrRefinementString + else super.toString + ) + if (Statistics.hotEnabled) Statistics.incCounter(classSymbolCount) } implicit val ClassSymbolTag = ClassTag[ClassSymbol](classOf[ClassSymbol]) diff --git a/src/reflect/scala/reflect/internal/TypeDebugging.scala b/src/reflect/scala/reflect/internal/TypeDebugging.scala index 71f84ab557..9c1342e68e 100644 --- a/src/reflect/scala/reflect/internal/TypeDebugging.scala +++ b/src/reflect/scala/reflect/internal/TypeDebugging.scala @@ -7,11 +7,73 @@ package scala package reflect package internal +import util.shortClassOfInstance + trait TypeDebugging { self: SymbolTable => - // @M toString that is safe during debugging (does not normalize, ...) + import definitions._ + + /** There's a whole lot of implementation detail which is nothing but noise when + * you are trying to see what's going on. This is my attempt to filter it out. + */ + object noPrint extends (Tree => Boolean) { + def skipScalaName(name: Name) = name match { + case tpnme.Any | tpnme.Nothing | tpnme.AnyRef => true + case _ => false + } + def skipRefTree(t: RefTree) = t match { + case Select(Select(Ident(nme.ROOTPKG), nme.scala_), name) if skipScalaName(name) => true + case Select(sel, name) if sel.symbol == ScalaPackage && skipScalaName(name) => true + case Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR) => true + case Ident(nme.ROOTPKG) => true + case _ => skipSym(t.symbol) + } + def skipSym(sym: Symbol): Boolean = sym match { + case null => false + case NothingClass | AnyClass => true + case PredefModule => true + case ObjectClass => true + case _ => sym.hasPackageFlag + } + def skipType(tpe: Type): Boolean = skipSym(tpe.typeSymbolDirect) + + def skip(t: Tree): Boolean = t match { + case EmptyTree => true + case PackageDef(_, _) => true + case t: RefTree => skipRefTree(t) + case TypeBoundsTree(lo, hi) => skip(lo) && skip(hi) + case Block(Nil, expr) => skip(expr) + case Apply(fn, Nil) => skip(fn) + case Block(stmt :: Nil, expr) => skip(stmt) && skip(expr) + case DefDef(_, nme.CONSTRUCTOR, Nil, Nil :: Nil, _, rhs) => skip(rhs) + case Literal(Constant(())) => true + case tt @ TypeTree() => skipType(tt.tpe) + case _ => skipSym(t.symbol) + } + def apply(t: Tree) = skip(t) + } + + /** Light color wrappers. + */ object typeDebug { + import scala.Console._ + + private val colorsOk = sys.props contains "scala.color" + private def inColor(s: String, color: String) = if (colorsOk && s != "") color + s + RESET else s + private def inBold(s: String, color: String) = if (colorsOk && s != "") color + BOLD + s + RESET else s + + def inLightRed(s: String) = inColor(s, RED) + def inLightGreen(s: String) = inColor(s, GREEN) + def inLightMagenta(s: String) = inColor(s, MAGENTA) + def inLightCyan(s: String): String = inColor(s, CYAN) + def inGreen(s: String): String = inBold(s, GREEN) + def inRed(s: String): String = inBold(s, RED) + def inBlue(s: String): String = inBold(s, BLUE) + def inCyan(s: String): String = inBold(s, CYAN) + def inMagenta(s: String) = inBold(s, MAGENTA) + def resetColor(s: String): String = if (colorsOk) s + RESET else s + private def to_s(x: Any): String = x match { // otherwise case classes are caught looking like products case _: Tree | _: Type => "" + x @@ -29,16 +91,32 @@ trait TypeDebugging { strs.mkString(label + " {\n ", "\n ", "\n}") } } - def ptLine(label: String, pairs: (String, Any)*): String = { - val strs = pairs map { case (k, v) => k + "=" + to_s(v) } - strs.mkString(label + ": ", ", ", "") + def ptLine(pairs: (String, Any)*): String = ( + pairs + map { case (k, v) => (k, to_s(v)) } + filterNot { case (_, v) => v == "" } + map { case ("", v) => v ; case (k, v) => s"$k=$v" } + mkString ", " + ) + def ptTree(t: Tree): String = t match { + case PackageDef(pid, _) => s"package $pid" + case ModuleDef(_, name, _) => s"object $name" + case DefDef(_, name, tparams, _, _, _) => "def " + name + ptTypeParams(tparams) + case ClassDef(_, name, Nil, _) if t.symbol != null && t.symbol.isModuleClass => s"module class $name" + case ClassDef(_, name, tparams, _) => "class " + name + ptTypeParams(tparams) + case td: TypeDef => ptTypeParam(td) + case TypeBoundsTree(lo, hi) => + val lo_s = if (noPrint(lo)) "" else " >: " + ptTree(lo) + val hi_s = if (noPrint(hi)) "" else " <: " + ptTree(hi) + lo_s + hi_s + case _ if (t.symbol eq null) || (t.symbol eq NoSymbol) => to_s(t) + case _ => "" + t.symbol.tpe } - def ptTree(t: Tree) = t match { - case PackageDef(pid, _) => "package " + pid - case ModuleDef(_, name, _) => "object " + name - case ClassDef(_, name, tparams, _) => "class " + name + str.brackets(tparams) - case _ => to_s(t) + def ptTypeParam(td: TypeDef): String = { + val TypeDef(mods, name, tparams, rhs) = td + name + ptTypeParams(tparams) + ptTree(rhs) } + def ptTypeParams(tparams: List[TypeDef]): String = str brackets (tparams map ptTypeParam) object str { def parentheses(xs: List[_]): String = xs.mkString("(", ", ", ")") @@ -46,19 +124,24 @@ trait TypeDebugging { def tparams(tparams: List[Type]): String = brackets(tparams map debug) def parents(ps: List[Type]): String = (ps map debug).mkString(" with ") def refine(defs: Scope): String = defs.toList.mkString("{", " ;\n ", "}") + def bounds(lo: Type, hi: Type): String = { + val lo_s = if (typeIsNothing(lo)) "" else s" >: $lo" + val hi_s = if (typeIsAny(hi)) "" else s" <: $hi" + lo_s + hi_s + } } - + import str._ private def debug(tp: Type): String = tp match { - case TypeRef(pre, sym, args) => debug(pre) + "." + sym.nameString + str.tparams(args) - case ThisType(sym) => sym.nameString + ".this" - case SingleType(pre, sym) => debug(pre) +"."+ sym.nameString +".type" - case RefinedType(parents, defs) => str.parents(parents) + str.refine(defs) - case ClassInfoType(parents, defs, clazz) => "class "+ clazz.nameString + str.parents(parents) + str.refine(defs) - case PolyType(tparams, result) => str.brackets(tparams) + " " + debug(result) - case TypeBounds(lo, hi) => ">: "+ debug(lo) +" <: "+ debug(hi) - case tv @ TypeVar(_, _) => tv.toString - case ExistentialType(tparams, qtpe) => "forSome "+ str.brackets(tparams) + " " + debug(qtpe) - case _ => "?"+tp.getClass.getName+"?"//tp.toString might produce cyclic error... + case TypeRef(pre, sym, args) => s"${debug(pre)}.${sym.nameString}.${tparams(args)}" + case ThisType(sym) => s"${sym.nameString}.this" + case SingleType(pre, sym) => s"${debug(pre)}.${sym.nameString}.type" + case RefinedType(ps, decls) => s"${parents(ps)} ${refine(decls)}" + case ClassInfoType(ps, decls, clazz) => s"class ${clazz.nameString} ${parents(ps)} ${refine(decls)}" + case PolyType(tparams, result) => s"${brackets(tparams)}${debug(result)}" + case TypeBounds(lo, hi) => bounds(lo, hi) + case tv @ TypeVar(_, _) => "" + tv + case ExistentialType(tparams, qtpe) => s"forSome ${brackets(tparams)} ${debug(qtpe)}" + case _ => s"?${shortClassOfInstance(tp)}?" // tp.toString might produce cyclic error... } def debugString(tp: Type) = debug(tp) } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 967146a130..11527d88ca 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -929,7 +929,7 @@ trait Types * after `maxTostringRecursions` recursion levels. Uses `safeToString` * to produce a string on each level. */ - override def toString: String = typeToString(this) + override final def toString: String = typeToString(this) /** Method to be implemented in subclasses. * Converts this type to a string in calling toString for its parts. @@ -943,7 +943,9 @@ trait Types else if ((str endsWith ".type") && !typeSymbol.isModuleClass) widen match { case RefinedType(_, _) => "" + widen - case _ => s"$str (with underlying type $widen)" + case _ => + if (widen.toString.trim == "") str + else s"$str (with underlying type $widen)" } else str } @@ -1557,10 +1559,10 @@ trait Types override def isStructuralRefinement: Boolean = typeSymbol.isAnonOrRefinementClass && (decls exists symbolIsPossibleInRefinement) - override def safeToString: String = parentsString(parents) + ( - (if (settings.debug || parents.isEmpty || (decls.elems ne null)) - fullyInitializeScope(decls).mkString("{", "; ", "}") else "") - ) + protected def shouldForceScope = settings.debug || parents.isEmpty || !decls.isEmpty + protected def initDecls = fullyInitializeScope(decls) + protected def scopeString = if (shouldForceScope) initDecls.mkString("{", "; ", "}") else "" + override def safeToString = parentsString(parents) + scopeString } protected def computeBaseClasses(tpe: Type): List[Symbol] = { @@ -1968,21 +1970,12 @@ trait Types } override def kind = "ClassInfoType" - - override def safeToString = - if (settings.debug || decls.size > 1) - formattedToString - else - super.safeToString - /** A nicely formatted string with newlines and such. */ - def formattedToString: String = - parents.mkString("\n with ") + ( - if (settings.debug || parents.isEmpty || (decls.elems ne null)) - fullyInitializeScope(decls).mkString(" {\n ", "\n ", "\n}") - else "" - ) + def formattedToString = parents.mkString("\n with ") + scopeString + override protected def shouldForceScope = settings.debug || decls.size > 1 + override protected def scopeString = initDecls.mkString(" {\n ", "\n ", "\n}") + override def safeToString = if (shouldForceScope) formattedToString else super.safeToString } object ClassInfoType extends ClassInfoTypeExtractor @@ -2370,7 +2363,6 @@ trait Types } thisInfo.decls } - protected[Types] def baseTypeSeqImpl: BaseTypeSeq = sym.info.baseTypeSeq map transform override def baseTypeSeq: BaseTypeSeq = { @@ -2385,7 +2377,6 @@ trait Types baseTypeSeqCache } } - // ensure that symbol is not a local copy with a name coincidence private def needsPreString = ( settings.debug @@ -2395,46 +2386,50 @@ trait Types private def preString = if (needsPreString) pre.prefixString else "" private def argsString = if (args.isEmpty) "" else args.mkString("[", ",", "]") - def refinementString = ( - if (sym.isStructuralRefinement) ( - fullyInitializeScope(decls) filter (sym => sym.isPossibleInRefinement && sym.isPublic) - map (_.defString) - mkString("{", "; ", "}") - ) + private def refinementDecls = fullyInitializeScope(decls) filter (sym => sym.isPossibleInRefinement && sym.isPublic) + private def refinementString = ( + if (sym.isStructuralRefinement) + refinementDecls map (_.defString) mkString("{", "; ", "}") else "" ) - protected def finishPrefix(rest: String) = ( if (sym.isInitialized && sym.isAnonymousClass && !phase.erasedTypes) parentsString(thisInfo.parents) + refinementString else rest ) + private def noArgsString = finishPrefix(preString + sym.nameString) + private def tupleTypeString: String = args match { + case Nil => noArgsString + case arg :: Nil => s"($arg,)" + case _ => args.mkString("(", ", ", ")") + } private def customToString = sym match { case RepeatedParamClass => args.head + "*" case ByNameParamClass => "=> " + args.head case _ => - def targs = dealiasWiden.typeArgs - - if (isFunctionType(this)) { + if (isFunctionTypeDirect(this)) { // Aesthetics: printing Function1 as T => R rather than (T) => R // ...but only if it's not a tuple, so ((T1, T2)) => R is distinguishable // from (T1, T2) => R. - targs match { - case in :: out :: Nil if !isTupleType(in) => - // A => B => C should be (A => B) => C or A => (B => C) + unspecializedTypeArgs(this) match { + // See neg/t588 for an example which arrives here - printing + // the type of a Function1 after erasure. + case Nil => noArgsString + case in :: out :: Nil if !isTupleTypeDirect(in) => + // A => B => C should be (A => B) => C or A => (B => C). // Also if A is byname, then we want (=> A) => B because => is right associative and => A => B // would mean => (A => B) which is a different type - val in_s = if (isFunctionType(in) || isByNameParamType(in)) "(" + in + ")" else "" + in - val out_s = if (isFunctionType(out)) "(" + out + ")" else "" + out + val in_s = if (isFunctionTypeDirect(in) || isByNameParamType(in)) "(" + in + ")" else "" + in + val out_s = if (isFunctionTypeDirect(out)) "(" + out + ")" else "" + out in_s + " => " + out_s case xs => xs.init.mkString("(", ", ", ")") + " => " + xs.last } } - else if (isTupleType(this)) - targs.mkString("(", ", ", if (hasLength(targs, 1)) ",)" else ")") - else if (sym.isAliasType && prefixChain.exists(_.termSymbol.isSynthetic) && (this ne this.normalize)) - "" + normalize + else if (isTupleTypeDirect(this)) + tupleTypeString + else if (sym.isAliasType && prefixChain.exists(_.termSymbol.isSynthetic) && (this ne dealias)) + "" + dealias else "" } |