diff options
author | Vlad Ureche <vlad.ureche@gmail.com> | 2012-07-12 14:24:54 +0200 |
---|---|---|
committer | Vlad Ureche <vlad.ureche@gmail.com> | 2012-07-16 23:41:44 +0200 |
commit | a119ad1ae58723bd2e757ed331a536ff4ae49bdf (patch) | |
tree | 45692e87e13f0870f8b6f928b33ff6668cd46389 /src | |
parent | 891769fae541513d68ce7a8e84b7213472c333c9 (diff) | |
download | scala-a119ad1ae58723bd2e757ed331a536ff4ae49bdf.tar.gz scala-a119ad1ae58723bd2e757ed331a536ff4ae49bdf.tar.bz2 scala-a119ad1ae58723bd2e757ed331a536ff4ae49bdf.zip |
SI-4360 Adds prefixes to scaladoc
This was a long-standing issue in scaladoc: It was unable to
disambiguate between entries with the same name. One example is:
immutable.Seq:
trait Seq[+A] extends Iterable[A] with Seq[A] ...
What's that? Seq extends Seq? No, immutable.Seq extends collection.Seq,
but scaladoc was unable to show that. Now it does, depending on the
template you're in. Prefixes are relative and can go back:
-scala.collection.Seq has subclasses *immutable.Seq* and *mutable.Seq*
-scala.immutable.Seq extends *collection.Seq*
Unfortunately the price we pay for this is high, a 20% slowdown in
scaladoc. This is why there is a new flag called -no-prefixes that
disables the prefixes in front of types.
Btw, it also fixes the notorious "booleanValue: This member is added by
an implicit conversion from Boolean to Boolean ...". That's now
java.lang.Boolean, so it becomes clear.
Conflicts:
src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala
Diffstat (limited to 'src')
11 files changed, 303 insertions, 200 deletions
diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 2cada92c1e..9aa2f6f921 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -153,6 +153,8 @@ class Scaladoc extends ScalaMatchingTask { /** Instruct the scaladoc to produce textual ouput from html pages, for easy diff-ing */ private var docRawOutput: Boolean = false + /** Instruct the scaladoc not to generate prefixes */ + private var docNoPrefixes: Boolean = false /*============================================================================*\ ** Properties setters ** @@ -427,6 +429,12 @@ class Scaladoc extends ScalaMatchingTask { def setRawOutput(input: String) = docRawOutput = Flag.getBooleanValue(input, "rawOutput") + /** Set the `noPrefixes` bit to prevent Scaladoc from generating prefixes in + * front of types -- may lead to confusion, but significantly speeds up the generation. + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setNoPrefixes(input: String) = + docNoPrefixes = Flag.getBooleanValue(input, "noPrefixes") + /*============================================================================*\ ** Properties getters ** \*============================================================================*/ @@ -625,6 +633,7 @@ class Scaladoc extends ScalaMatchingTask { docSettings.docDiagrams.value = docDiagrams docSettings.docDiagramsDebug.value = docDiagramsDebug docSettings.docRawOutput.value = docRawOutput + docSettings.docNoPrefixes.value = docNoPrefixes if(!docDiagramsDotPath.isEmpty) docSettings.docDiagramsDotPath.value = docDiagramsDotPath.get if (!docgenerator.isEmpty) docSettings.docgenerator.value = docgenerator.get diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 3c92c3b4b6..964227a6a5 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -81,6 +81,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor new { override val global: compiler.type = compiler } with model.ModelFactory(compiler, settings) with model.ModelFactoryImplicitSupport + with model.ModelFactoryTypeSupport with model.diagram.DiagramFactory with model.comment.CommentFactory with model.TreeFactory { diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 31e49131f6..c7bdf74ebd 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -166,6 +166,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "For each html file, create another .html.raw file containing only the text. (can be used for quickly diffing two scaladoc outputs)" ) + val docNoPrefixes = BooleanSetting ( + "-no-prefixes", + "Prevents generating prefixes in types, possibly creating ambiguous references, but significantly speeding up scaladoc." + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -177,7 +182,8 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docDiagrams, docDiagramsDebug, docDiagramsDotPath, docDiagramsDotTimeout, docDiagramsDotRestart, docImplicits, docImplicitsDebug, docImplicitsShowAll, - docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses + docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, + docNoPrefixes ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index 59560befc9..f3454f71b8 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -86,22 +86,22 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { // them by on node with a corresponding tooltip superClasses = if (_superClasses.length > settings.docDiagramsMaxNormalClasses.value) { val superClassesTooltip = Some(limitSize(_superClasses.map(_.tpe.name).mkString(", "))) - List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None, superClassesTooltip)) + List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None)(superClassesTooltip)) } else _superClasses subClasses = if (_subClasses.length > settings.docDiagramsMaxNormalClasses.value) { val subClassesTooltip = Some(limitSize(_subClasses.map(_.tpe.name).mkString(", "))) - List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None, subClassesTooltip)) + List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None)(subClassesTooltip)) } else _subClasses incomingImplicits = if (_incomingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { val incomingImplicitsTooltip = Some(limitSize(_incomingImplicits.map(_.tpe.name).mkString(", "))) - List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None, incomingImplicitsTooltip)) + List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None)(incomingImplicitsTooltip)) } else _incomingImplicits outgoingImplicits = if (_outgoingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { val outgoingImplicitsTooltip = Some(limitSize(_outgoingImplicits.map(_.tpe.name).mkString(", "))) - List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None, outgoingImplicitsTooltip)) + List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None)(outgoingImplicitsTooltip)) } else _outgoingImplicits thisNode = _thisNode diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala index 37600fa908..adfeb3b012 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala @@ -28,7 +28,7 @@ class DotRunner(settings: doc.Settings) { if (dotProcess == null) { if (dotRestarts < settings.docDiagramsDotRestart.value) { if (dotRestarts != 0) - settings.printMsg("A new graphviz dot process will be created...\n") + settings.printMsg("Graphviz will be restarted...\n") dotRestarts += 1 dotProcess = new DotProcess(settings) } else @@ -145,9 +145,10 @@ class DotProcess(settings: doc.Settings) { settings.printMsg("**********************************************************************") } else { // we shouldn't just sit there for 50s not reporting anything, no? - settings.printMsg("Graphviz dot encountered an error when generating the diagram for") - settings.printMsg(templateName + ". Use the " + settings.docDiagramsDebug.name + " flag") - settings.printMsg("for more information.") + settings.printMsg("Graphviz dot encountered an error when generating the diagram for:") + settings.printMsg(templateName) + settings.printMsg("These are usually spurious errors, but if you notice a persistant error on") + settings.printMsg("a diagram, please use the " + settings.docDiagramsDebug.name + " flag and report a bug with the output.") } } } diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 4ab77b356b..41ba95e072 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -96,9 +96,6 @@ trait TemplateEntity extends Entity { /** The self-type of this template, if it differs from the template type. */ def selfType : Option[TypeEntity] - - /** The type of this entity, with type members */ - def ownType: TypeEntity } @@ -206,7 +203,8 @@ trait HigherKinded { /** A template (class, trait, object or package) which is referenced in the universe, but for which no further * documentation is available. Only templates for which a source file is given are documented by Scaladoc. */ trait NoDocTemplate extends TemplateEntity { - def kind = "<no doc>" + def kind = "" + //def kind = "(not documented) template" } /** An inherited template that was not documented in its original owner - example: @@ -214,7 +212,8 @@ trait NoDocTemplate extends TemplateEntity { * in the source: trait U extends T -- C appears in U as a NoDocTemplateMemberImpl * -- that is, U has a member for it but C doesn't get its own page */ trait NoDocTemplateMemberEntity extends TemplateEntity with MemberEntity { - def kind = "<no doc, mbr>" + def kind = "" + //def kind = "(not documented) member template" } /** A template (class, trait, object or package) for which documentation is available. Only templates for which diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 25b4a174ec..b7c4eed87d 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -19,7 +19,12 @@ import model.{ RootPackage => RootPackageEntity } /** This trait extracts all required information for documentation from compilation units */ class ModelFactory(val global: Global, val settings: doc.Settings) { - thisFactory: ModelFactory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with TreeFactory => + thisFactory: ModelFactory + with ModelFactoryImplicitSupport + with ModelFactoryTypeSupport + with DiagramFactory + with CommentFactory + with TreeFactory => import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } @@ -32,7 +37,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { private var universe: Universe = null private def dbg(msg: String) = if (sys.props contains "scala.scaladoc.debug") println(msg) - private def closestPackage(sym: Symbol) = { + protected def closestPackage(sym: Symbol) = { if (sym.isPackage || sym.isPackageClass) sym else sym.enclosingPackage } @@ -63,7 +68,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { private val droppedPackages = mutable.Set[PackageImpl]() protected val docTemplatesCache = new mutable.LinkedHashMap[Symbol, DocTemplateImpl] protected val noDocTemplatesCache = new mutable.LinkedHashMap[Symbol, NoDocTemplateImpl] - protected var typeCache = new mutable.LinkedHashMap[Type, TypeEntity] + def packageDropped(tpl: DocTemplateImpl) = tpl match { + case p: PackageImpl => droppedPackages(p) + case _ => false + } def optimize(str: String): String = if (str.length < 16) str.intern else str @@ -95,7 +103,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isObject = sym.isModule && !sym.isPackage def isCaseClass = sym.isCaseClass def isRootPackage = false - def ownType = makeType(sym.tpe, this) def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this)) } @@ -338,14 +345,14 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def directSubClasses = allSubClasses.filter(_.parentTypes.map(_._1).contains(this)) /* Implcitly convertible class cache */ - private var implicitlyConvertibleClassesCache: mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)] = null - def registerImplicitlyConvertibleClass(dtpl: DocTemplateEntity, conv: ImplicitConversionImpl): Unit = { + private var implicitlyConvertibleClassesCache: mutable.ListBuffer[(DocTemplateImpl, ImplicitConversionImpl)] = null + def registerImplicitlyConvertibleClass(dtpl: DocTemplateImpl, conv: ImplicitConversionImpl): Unit = { if (implicitlyConvertibleClassesCache == null) - implicitlyConvertibleClassesCache = mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)]() + implicitlyConvertibleClassesCache = mutable.ListBuffer[(DocTemplateImpl, ImplicitConversionImpl)]() implicitlyConvertibleClassesCache += ((dtpl, conv)) } - def incomingImplicitlyConvertedClasses: List[(DocTemplateEntity, ImplicitConversionImpl)] = + def incomingImplicitlyConvertedClasses: List[(DocTemplateImpl, ImplicitConversionImpl)] = if (implicitlyConvertibleClassesCache == null) List() else @@ -408,7 +415,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { conv.targetTypeComponents map { case pair@(template, tpe) => template match { - case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this, conv) + case d: DocTemplateImpl if (d != this) => d.registerImplicitlyConvertibleClass(this, conv) case _ => // nothing } (pair._1, pair._2, conv) @@ -508,6 +515,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { normalizeTemplate(aSym.owner) case _ if aSym.isModuleClass => normalizeTemplate(aSym.sourceModule) + // case t: ThisType => + // t. case _ => aSym } @@ -737,12 +746,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def findTemplate(query: String): Option[DocTemplateImpl] = { assert(modelFinished) - docTemplatesCache.values find { (tpl: TemplateImpl) => tpl.qualifiedName == query && !tpl.isObject } + docTemplatesCache.values find { (tpl: DocTemplateImpl) => tpl.qualifiedName == query && !packageDropped(tpl) && !tpl.isObject } } def findTemplateMaybe(aSym: Symbol): Option[DocTemplateImpl] = { assert(modelFinished) - docTemplatesCache.get(normalizeTemplate(aSym)) + docTemplatesCache.get(normalizeTemplate(aSym)).filterNot(packageDropped(_)) } def makeTemplate(aSym: Symbol): TemplateImpl = makeTemplate(aSym, None) @@ -890,158 +899,25 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { List((makeTemplate(aType.typeSymbol), makeType(aType, inTpl))) } - /** */ - def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { - def templatePackage = closestPackage(inTpl.sym) - - def createTypeEntity = new TypeEntity { - private val nameBuffer = new StringBuilder - private var refBuffer = new immutable.TreeMap[Int, (LinkTo, Int)] - private def appendTypes0(types: List[Type], sep: String): Unit = types match { - case Nil => - case tp :: Nil => - appendType0(tp) - case tp :: tps => - appendType0(tp) - nameBuffer append sep - appendTypes0(tps, sep) - } - - private def appendType0(tpe: Type): Unit = tpe match { - /* Type refs */ - case tp: TypeRef if definitions.isFunctionType(tp) => - val args = tp.normalize.typeArgs - nameBuffer append '(' - appendTypes0(args.init, ", ") - nameBuffer append ") ⇒ " - appendType0(args.last) - case tp: TypeRef if definitions.isScalaRepeatedParamType(tp) => - appendType0(tp.args.head) - nameBuffer append '*' - case tp: TypeRef if definitions.isByNameParamType(tp) => - nameBuffer append "⇒ " - appendType0(tp.args.head) - case tp: TypeRef if definitions.isTupleType(tp) => - val args = tp.normalize.typeArgs - nameBuffer append '(' - appendTypes0(args, ", ") - nameBuffer append ')' - case TypeRef(pre, aSym, targs) => - val preSym = pre.widen.typeSymbol - // There's a work in progress here trying to deal with the - // places where undesirable prefixes are printed. - // ... - // If the prefix is something worthy of printing, see if the prefix type - // is in the same package as the enclosing template. If so, print it - // unqualified and they'll figure it out. - // - // val stripPrefixes = List(templatePackage.fullName + ".", "package.", "java.lang.") - // if (!preSym.printWithoutPrefix) { - // nameBuffer append stripPrefixes.foldLeft(pre.prefixString)(_ stripPrefix _) - // } - - // SI-3314/SI-4888: Classes, Traits and Types can be inherited from a template to another: - // class Enum { abstract class Value } - // class Day extends Enum { object Mon extends Value /*...*/ } - // ===> in such cases we have two options: - // (0) if there's no inheritance taking place (Enum#Value) we can link to the template directly - // (1) if we generate the doc template for Day, we can link to the correct member - // (2) if we don't generate the doc template, we should at least indicate the correct prefix in the tooltip - val bSym = normalizeTemplate(aSym) - val owner = - if ((preSym != NoSymbol) && /* it needs a prefix */ - (preSym != bSym.owner) && /* prefix is different from owner */ - // ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */ - // (preSym != inTpl.sym.moduleClass)) && /* or object */ - (aSym == bSym)) /* normalization doesn't play tricks on us */ - preSym - else - bSym.owner - - val bTpl = findTemplateMaybe(bSym) - val link = - if (owner == bSym.owner && bTpl.isDefined) - // (0) the owner's class is linked AND has a template - lovely - LinkToTpl(bTpl.get) - else { - val oTpl = findTemplateMaybe(owner) - val bMbr = oTpl.map(findMember(bSym, _)) - if (oTpl.isDefined && bMbr.isDefined && bMbr.get.isDefined) - // (1) the owner's class - LinkToMember(bMbr.get.get, oTpl.get) //ugh - else - // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!) - LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner)))) - } - - // TODO: The name might include a prefix, take care of that! - val name = bSym.nameString - val pos0 = nameBuffer.length - refBuffer += pos0 -> ((link, name.length)) - nameBuffer append name - - // if (bSym.isNonClassType && bSym != AnyRefClass) { - // nameBuffer append bSym.decodedName - // } else { - // val tpl = makeTemplate(bSym) - // val pos0 = nameBuffer.length - // refBuffer += pos0 -> ((LinkToTpl(tpl), tpl.name.length)) - // nameBuffer append tpl.name - // } - - if (!targs.isEmpty) { - nameBuffer append '[' - appendTypes0(targs, ", ") - nameBuffer append ']' - } - /* Refined types */ - case RefinedType(parents, defs) => - val ignoreParents = Set[Symbol](AnyClass, ObjectClass) - val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) match { - case Nil => parents - case ps => ps - } - appendTypes0(filtParents, " with ") - // XXX Still todo: properly printing refinements. - // Since I didn't know how to go about displaying a multi-line type, I went with - // printing single method refinements (which should be the most common) and printing - // the number of members if there are more. - defs.toList match { - case Nil => () - case x :: Nil => nameBuffer append (" { " + x.defString + " }") - case xs => nameBuffer append (" { ... /* %d definitions in type refinement */ }" format xs.size) - } - /* Eval-by-name types */ - case NullaryMethodType(result) => - nameBuffer append '⇒' - appendType0(result) - /* Polymorphic types */ - case PolyType(tparams, result) => assert(tparams.nonEmpty) -// throw new Error("Polymorphic type '" + tpe + "' cannot be printed as a type") - def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else - tps.map{tparam => - tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams) - }.mkString("[", ", ", "]") - nameBuffer append typeParamsToString(tparams) - appendType0(result) - case tpen => - nameBuffer append tpen.toString + def makeQualifiedName(sym: Symbol, relativeTo: Option[Symbol] = None): String = { + val stop = if (relativeTo.isDefined) relativeTo.get.ownerChain.toSet else Set[Symbol]() + var sym1 = sym + var path = new StringBuilder() + // var path = List[Symbol]() + + while ((sym1 != NoSymbol) && (path.isEmpty || !stop(sym1))) { + val sym1Norm = normalizeTemplate(sym1) + if (!sym1.sourceModule.isPackageObject && sym1Norm != RootPackage) { + if (path.length != 0) + path.insert(0, ".") + path.insert(0, sym1Norm.nameString) + // path::= sym1Norm } - appendType0(aType) - val refEntity = refBuffer - val name = optimize(nameBuffer.toString) + sym1 = sym1.owner } - if (aType.isTrivial) - typeCache.get(aType) match { - case Some(typeEntity) => typeEntity - case None => - val typeEntity = createTypeEntity - typeCache += aType -> typeEntity - typeEntity - } - else - createTypeEntity + optimize(path.toString) + //path.mkString(".") } def normalizeOwner(aSym: Symbol): Symbol = diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 493ad3d831..ddcdf1cf5c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -53,7 +53,7 @@ import model.{ RootPackage => RootPackageEntity } * TODO: Give an overview here */ trait ModelFactoryImplicitSupport { - thisFactory: ModelFactory with CommentFactory with TreeFactory => + thisFactory: ModelFactory with ModelFactoryTypeSupport with CommentFactory with TreeFactory => import global._ import global.analyzer._ @@ -329,11 +329,6 @@ trait ModelFactoryImplicitSupport { } } - def makeQualifiedName(sym: Symbol): String = { - val remove = Set[Symbol](RootPackage, RootClass, EmptyPackage, EmptyPackageClass) - sym.ownerChain.filterNot(remove.contains(_)).reverse.map(_.nameString).mkString(".") - } - /* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */ class ImplicitConversionImpl( diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala new file mode 100644 index 0000000000..2bc9f070b1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -0,0 +1,215 @@ +/* NSC -- new Scala compiler -- Copyright 2007-2011 LAMP/EPFL */ + +package scala.tools.nsc +package doc +package model + +import comment._ + +import diagram._ + +import scala.collection._ +import scala.util.matching.Regex + +import symtab.Flags + +import io._ + +import model.{ RootPackage => RootPackageEntity } + +/** This trait extracts all required information for documentation from compilation units */ +trait ModelFactoryTypeSupport { + thisFactory: ModelFactory + with ModelFactoryImplicitSupport + with ModelFactoryTypeSupport + with DiagramFactory + with CommentFactory + with TreeFactory => + + import global._ + import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } + import rootMirror.{ RootPackage, RootClass, EmptyPackage } + + protected var typeCache = new mutable.LinkedHashMap[(Type, TemplateImpl), TypeEntity] + protected var typeCacheNoPrefix = new mutable.LinkedHashMap[Type, TypeEntity] + + /** */ + def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { + def templatePackage = closestPackage(inTpl.sym) + + def createTypeEntity = new TypeEntity { + private var nameBuffer = new StringBuilder + private var refBuffer = new immutable.TreeMap[Int, (LinkTo, Int)] + private def appendTypes0(types: List[Type], sep: String): Unit = types match { + case Nil => + case tp :: Nil => + appendType0(tp) + case tp :: tps => + appendType0(tp) + nameBuffer append sep + appendTypes0(tps, sep) + } + + private def appendType0(tpe: Type): Unit = tpe match { + /* Type refs */ + case tp: TypeRef if definitions.isFunctionType(tp) => + val args = tp.normalize.typeArgs + nameBuffer append '(' + appendTypes0(args.init, ", ") + nameBuffer append ") ⇒ " + appendType0(args.last) + case tp: TypeRef if definitions.isScalaRepeatedParamType(tp) => + appendType0(tp.args.head) + nameBuffer append '*' + case tp: TypeRef if definitions.isByNameParamType(tp) => + nameBuffer append "⇒ " + appendType0(tp.args.head) + case tp: TypeRef if definitions.isTupleType(tp) => + val args = tp.normalize.typeArgs + nameBuffer append '(' + appendTypes0(args, ", ") + nameBuffer append ')' + case TypeRef(pre, aSym, targs) => + val preSym = pre.widen.typeSymbol + + // SI-3314/SI-4888: Classes, Traits and Types can be inherited from a template to another: + // class Enum { abstract class Value } + // class Day extends Enum { object Mon extends Value /*...*/ } + // ===> in such cases we have two options: + // (0) if there's no inheritance taking place (Enum#Value) we can link to the template directly + // (1) if we generate the doc template for Day, we can link to the correct member + // (2) if we don't generate the doc template, we should at least indicate the correct prefix in the tooltip + val bSym = normalizeTemplate(aSym) + val owner = + if ((preSym != NoSymbol) && /* it needs a prefix */ + (preSym != bSym.owner) && /* prefix is different from owner */ + // ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */ + // (preSym != inTpl.sym.moduleClass)) && /* or object */ + (aSym == bSym)) /* normalization doesn't play tricks on us */ + preSym + else + bSym.owner + + val bTpl = findTemplateMaybe(bSym) + val link = + if (owner == bSym.owner && bTpl.isDefined) + // (0) the owner's class is linked AND has a template - lovely + LinkToTpl(bTpl.get) + else { + val oTpl = findTemplateMaybe(owner) + val bMbr = oTpl.map(findMember(bSym, _)) + if (oTpl.isDefined && bMbr.isDefined && bMbr.get.isDefined) + // (1) the owner's class + LinkToMember(bMbr.get.get, oTpl.get) //ugh + else + // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!) + LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner)))) + } + + // SI-4360 Showing prefixes when necessary + // We check whether there's any directly accessible type with the same name in the current template OR if the + // type is inherited from one template to another. There may be multiple symbols with the same name in scope, + // but we won't show the prefix if our symbol is among them, only if *it's not* -- that's equal to showing + // the prefix only for ambiguous references, not for overloaded ones. + def needsPrefix: Boolean = { + if (owner != bSym.owner && (normalizeTemplate(owner) != inTpl.sym)) + return true + + for (tpl <- inTpl.sym.ownerChain) { + tpl.info.member(bSym.name) match { + case NoSymbol => + // No syms with that name, look further inside the owner chain + case sym => + // Symbol found -- either the correct symbol, another one OR an overloaded alternative + if (sym == bSym) + return false + else sym.info match { + case OverloadedType(owner, alternatives) => + return alternatives.contains(bSym) + case _ => + return true + } + } + } + // if it's not found in the owner chain, we can safely leave out the prefix + false + } + + val prefix = + if (!settings.docNoPrefixes.value && needsPrefix && (bSym != AnyRefClass /* which we normalize */)) { + val qualifiedName = makeQualifiedName(owner, Some(inTpl.sym)) + if (qualifiedName != "") qualifiedName + "." else "" + } else "" + + //DEBUGGING: + //if (makeQualifiedName(bSym) == "pack1.A") println("needsPrefix(" + bSym + ", " + owner + ", " + inTpl.qualifiedName + ") => " + needsPrefix + " and prefix=" + prefix) + + val name = prefix + bSym.nameString + val pos0 = nameBuffer.length + refBuffer += pos0 -> ((link, name.length)) + nameBuffer append name + + if (!targs.isEmpty) { + nameBuffer append '[' + appendTypes0(targs, ", ") + nameBuffer append ']' + } + /* Refined types */ + case RefinedType(parents, defs) => + val ignoreParents = Set[Symbol](AnyClass, ObjectClass) + val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) match { + case Nil => parents + case ps => ps + } + appendTypes0(filtParents, " with ") + // XXX Still todo: properly printing refinements. + // Since I didn't know how to go about displaying a multi-line type, I went with + // printing single method refinements (which should be the most common) and printing + // the number of members if there are more. + defs.toList match { + case Nil => () + case x :: Nil => nameBuffer append (" { " + x.defString + " }") + case xs => nameBuffer append (" { ... /* %d definitions in type refinement */ }" format xs.size) + } + /* Eval-by-name types */ + case NullaryMethodType(result) => + nameBuffer append '⇒' + appendType0(result) + /* Polymorphic types */ + case PolyType(tparams, result) => assert(tparams.nonEmpty) +// throw new Error("Polymorphic type '" + tpe + "' cannot be printed as a type") + def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else + tps.map{tparam => + tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams) + }.mkString("[", ", ", "]") + nameBuffer append typeParamsToString(tparams) + appendType0(result) + case tpen => + nameBuffer append tpen.toString + } + appendType0(aType) + val refEntity = refBuffer + val name = optimize(nameBuffer.toString) + nameBuffer = null + } + + // SI-4360: Entity caching depends on both the type AND the template it's in, as the prefixes might change for the + // same type based on the template the type is shown in. + val cached = + if (!settings.docNoPrefixes.value) + typeCache.get((aType, inTpl)) + else + typeCacheNoPrefix.get(aType) + + cached match { + case Some(typeEntity) => typeEntity + case None => + val typeEntity = createTypeEntity + if (!settings.docNoPrefixes.value) + typeCache += (aType, inTpl) -> typeEntity + else + typeCacheNoPrefix += aType -> typeEntity + typeEntity + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala index 2b804ca10f..902d5da240 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -98,20 +98,20 @@ object OtherNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEn /** The node for the current class */ -case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isThisNode = true } +case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isThisNode = true } /** The usual node */ -case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isNormalNode = true } +case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isNormalNode = true } /** A class or trait the thisnode can be converted to by an implicit conversion * TODO: I think it makes more sense to use the tpe links to templates instead of the TemplateEntity for implicit nodes * since some implicit conversions convert the class to complex types that cannot be represented as a single tmeplate */ -case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isImplicitNode = true } +case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isImplicitNode = true } /** An outside node is shown in packages when a class from a different package makes it to the package diagram due to * its relation to a class in the template (see @contentDiagram hideInheritedNodes annotation) */ -case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isOutsideNode = true } +case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isOutsideNode = true } // Computing and offering node depth information diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index 731801b143..d0b363854c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -18,21 +18,14 @@ import scala.collection.immutable.SortedMap * @author Vlad Ureche */ trait DiagramFactory extends DiagramDirectiveParser { - this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory => + this: ModelFactory with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with TreeFactory => import this.global.definitions._ import this.global._ // the following can used for hardcoding different relations into the diagram, for bootstrapping purposes - lazy val AnyNode = normalNode(AnyClass) - lazy val AnyRefNode = normalNode(AnyRefClass) - lazy val AnyValNode = normalNode(AnyValClass) - lazy val NullNode = normalNode(NullClass) - lazy val NothingNode = normalNode(NothingClass) - def normalNode(sym: Symbol) = - NormalNode(makeTemplate(sym).ownType, Some(makeTemplate(sym))) def aggregationNode(text: String) = - NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None) + NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None)() /** Create the inheritance diagram for this template */ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { @@ -52,31 +45,31 @@ trait DiagramFactory extends DiagramDirectiveParser { None else { // the main node - val thisNode = ThisNode(tpl.ownType, Some(tpl), Some(tpl.qualifiedName + " (this " + tpl.kind + ")")) + val thisNode = ThisNode(tpl.resultType, Some(tpl))(Some(tpl.qualifiedName + " (this " + tpl.kind + ")")) // superclasses var superclasses: List[Node] = tpl.parentTypes.collect { - case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => NormalNode(p._2, Some(p._1)) + case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => NormalNode(p._2, Some(p._1))() }.reverse // incoming implcit conversions lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map { case (incomingTpl, conv) => - ImplicitNode(incomingTpl.ownType, Some(incomingTpl), implicitTooltip(from=incomingTpl, to=tpl, conv=conv)) + ImplicitNode(makeType(incomingTpl.sym.tpe, tpl), Some(incomingTpl))(implicitTooltip(from=incomingTpl, to=tpl, conv=conv)) } // subclasses var subclasses: List[Node] = tpl.directSubClasses.flatMap { - case d: TemplateEntity if !classExcluded(d) => List(NormalNode(d.ownType, Some(d))) + case d: TemplateImpl if !classExcluded(d) => List(NormalNode(makeType(d.sym.tpe, tpl), Some(d))()) case _ => Nil }.sortBy(_.tpl.get.name)(implicitly[Ordering[String]].reverse) // outgoing implicit coversions lazy val outgoingImplicitNodes = tpl.outgoingImplicitlyConvertedClasses.map { case (outgoingTpl, outgoingType, conv) => - ImplicitNode(outgoingType, Some(outgoingTpl), implicitTooltip(from=tpl, to=tpl, conv=conv)) + ImplicitNode(outgoingType, Some(outgoingTpl))(implicitTooltip(from=tpl, to=tpl, conv=conv)) } // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. @@ -149,7 +142,12 @@ trait DiagramFactory extends DiagramDirectiveParser { case _ => } - mapNodes += node -> (if (node.inTemplate == pack) NormalNode(node.ownType, Some(node)) else OutsideNode(node.ownType, Some(node))) + mapNodes += node -> ( + if (node.inTemplate == pack) + NormalNode(node.resultType, Some(node))() + else + OutsideNode(node.resultType, Some(node))() + ) } if (nodesShown.isEmpty) @@ -173,7 +171,10 @@ trait DiagramFactory extends DiagramDirectiveParser { val anyRefSubtypes = Nil val allAnyRefTypes = aggregationNode("All AnyRef subtypes") val nullTemplate = makeTemplate(NullClass) - ContentDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) + if (nullTemplate.isDocTemplate) + ContentDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) + else + ContentDiagram(nodes, edges) } else ContentDiagram(nodes, edges) |