From 44ec110bf059a089f54c06469ff2a54275d0f05f Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Sat, 16 Jun 2012 18:47:40 +0200 Subject: Scaladoc diff-firendly output Scaladoc can create raw content files that we can easily diff and spot any modifications. There is a cool project by Stefan Zeiger to export the scaladoc model in JSON, but with the language and scaladoc being so quick to evolve, it'll be a pain to properly maintain. In the long-run, the plan is to sample a couple of raw files on each build and email me the diff. If I spot anything that may be wrong I can fix it, revert the commit or at least file a bug. For now, .html.raw files are generated on-demand, using ant -Dscaladoc.raw.output="yes" Also added a script that will do the job of diff-ing. Review by @jsuereth. Conflicts: src/compiler/scala/tools/nsc/doc/Settings.scala --- build.xml | 17 +++++--- src/compiler/scala/tools/ant/Scaladoc.scala | 9 ++++ src/compiler/scala/tools/nsc/doc/Settings.scala | 5 +++ .../scala/tools/nsc/doc/html/HtmlPage.scala | 18 ++++---- src/compiler/scala/tools/nsc/doc/html/Page.scala | 18 +++++++- .../tools/nsc/doc/html/page/IndexScript.scala | 10 +---- tools/scaladoc-compare | 50 ++++++++++++++++++++++ 7 files changed, 102 insertions(+), 25 deletions(-) create mode 100755 tools/scaladoc-compare diff --git a/build.xml b/build.xml index 33ebbfe377..e6b23f268f 100644 --- a/build.xml +++ b/build.xml @@ -2064,6 +2064,9 @@ DOCUMENTATION + + + @@ -2091,7 +2094,7 @@ DOCUMENTATION classpathref="pack.classpath" addparams="${scalac.args.all}" docRootContent="${src.dir}/library/rootdoc.txt" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> @@ -2175,7 +2178,7 @@ DOCUMENTATION srcdir="${src.dir}/compiler" docRootContent="${src.dir}/compiler/rootdoc.txt" addparams="${scalac.args.all}" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> @@ -2197,7 +2200,7 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/jline/src/main/java" addparams="${scalac.args.all}" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> @@ -2221,7 +2224,7 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/scalap" addparams="${scalac.args.all}" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> @@ -2243,7 +2246,7 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/partest" addparams="${scalac.args.all}" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> @@ -2265,7 +2268,7 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/continuations/plugin" addparams="${scalac.args.all}" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> @@ -2287,7 +2290,7 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/actors-migration" addparams="${scalac.args.all}" - implicits="on" diagrams="on"> + implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index daa08ef8a7..2cada92c1e 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -150,6 +150,9 @@ class Scaladoc extends ScalaMatchingTask { /** Instruct the scaladoc tool to use the binary given to create diagrams */ private var docDiagramsDotPath: Option[String] = None + /** Instruct the scaladoc to produce textual ouput from html pages, for easy diff-ing */ + private var docRawOutput: Boolean = false + /*============================================================================*\ ** Properties setters ** @@ -419,6 +422,11 @@ class Scaladoc extends ScalaMatchingTask { def setDiagramsDotPath(input: String) = docDiagramsDotPath = Some(input) + /** Set the `rawOutput` bit so Scaladoc also outputs text from each html file + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setRawOutput(input: String) = + docRawOutput = Flag.getBooleanValue(input, "rawOutput") + /*============================================================================*\ ** Properties getters ** \*============================================================================*/ @@ -616,6 +624,7 @@ class Scaladoc extends ScalaMatchingTask { docSettings.docImplicitsShowAll.value = docImplicitsShowAll docSettings.docDiagrams.value = docDiagrams docSettings.docDiagramsDebug.value = docDiagramsDebug + docSettings.docRawOutput.value = docRawOutput 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/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 4458889d55..d7b61bc129 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -120,6 +120,11 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { "dot" // by default, just pick up the system-wide dot ) + val docRawOutput = BooleanSetting ( + "-raw-output", + "For each html file, create another .html.raw file containing only the text. (can be used for quickly diffing two scaladoc outputs)" + ) + // 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. diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index e3da8bddea..4c9215f923 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -13,7 +13,7 @@ import comment._ import xml.{XML, NodeSeq} import xml.dtd.{DocType, PublicID} import scala.collection._ -import java.nio.channels.Channels +import java.io.Writer /** An html page that is part of a Scaladoc site. * @author David Bernard @@ -52,17 +52,19 @@ abstract class HtmlPage extends Page { thisPage => { body } - val fos = createFileOutputStream(site) - val w = Channels.newWriter(fos.getChannel, site.encoding) - try { + + writeFile(site) { (w: Writer) => w.write("\n") w.write(doctype.toString + "\n") w.write(xml.Xhtml.toXhtml(html)) } - finally { - w.close() - fos.close() - } + + if (site.universe.settings.docRawOutput.value) + writeFile(site, ".raw") { + // we're only interested in the body, as this will go into the diff + _.write(body.text) + } + //XML.save(pageFile.getPath, html, site.encoding, xmlDecl = false, doctype = doctype) } diff --git a/src/compiler/scala/tools/nsc/doc/html/Page.scala b/src/compiler/scala/tools/nsc/doc/html/Page.scala index c5bf3e0e37..72b62dd482 100644 --- a/src/compiler/scala/tools/nsc/doc/html/Page.scala +++ b/src/compiler/scala/tools/nsc/doc/html/Page.scala @@ -8,6 +8,8 @@ package scala.tools.nsc.doc.html import scala.tools.nsc.doc.model._ import java.io.{FileOutputStream, File} import scala.reflect.NameTransformer +import java.nio.channels.Channels +import java.io.Writer abstract class Page { thisPage => @@ -20,8 +22,8 @@ abstract class Page { def absoluteLinkTo(path: List[String]) = path.reverse.mkString("/") - def createFileOutputStream(site: HtmlFactory) = { - val file = new File(site.siteRoot, absoluteLinkTo(thisPage.path)) + def createFileOutputStream(site: HtmlFactory, suffix: String = "") = { + val file = new File(site.siteRoot, absoluteLinkTo(thisPage.path) + suffix) val folder = file.getParentFile if (! folder.exists) { folder.mkdirs @@ -29,6 +31,18 @@ abstract class Page { new FileOutputStream(file.getPath) } + def writeFile(site: HtmlFactory, suffix: String = "")(fn: Writer => Unit) = { + val fos = createFileOutputStream(site, suffix) + val w = Channels.newWriter(fos.getChannel, site.encoding) + try { + fn(w) + } + finally { + w.close() + fos.close() + } + } + /** Writes this page as a file. The file's location is relative to the * generator's site root, and the encoding is also defined by the generator. * @param generator The generator that is writing this page. */ diff --git a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala index 7edd4937c4..c5fe4da17a 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala @@ -15,14 +15,8 @@ class IndexScript(universe: doc.Universe, index: doc.Index) extends Page { def path = List("index.js") override def writeFor(site: HtmlFactory) { - val stream = createFileOutputStream(site) - val writer = Channels.newWriter(stream.getChannel, site.encoding) - try { - writer.write("Index.PACKAGES = " + packages.toString() + ";") - } - finally { - writer.close - stream.close + writeFile(site) { + _.write("Index.PACKAGES = " + packages.toString() + ";") } } diff --git a/tools/scaladoc-compare b/tools/scaladoc-compare new file mode 100755 index 0000000000..74fbfd1dd4 --- /dev/null +++ b/tools/scaladoc-compare @@ -0,0 +1,50 @@ +#!/bin/bash +# +# Script to compare scaladoc raw files. For an explanation read the next echos. +# + +if [ $# -ne 2 ] +then + echo + echo "scaladoc-compare will compare the scaladoc-generated pages in two different locations and output the diff" + echo "it's main purpose is to track changes to scaladoc and prevent updates that break things." + echo + echo "This script is meant to be used with the scaladoc -raw-output option, as it compares .html.raw files " + echo "instead of markup-heavy .html files." + echo + echo "Script usage $0 " + echo " eg: $0 build/scaladoc/library build/scaladoc-prev/library | less" + echo + exit 1 +fi + +NEW_PATH=$1 +OLD_PATH=$2 + +FILES=`find $NEW_PATH -name '*.html.raw'` +if [ "$FILES" == "" ] +then + echo "No .html.raw files found in $NEW_PATH!" + exit 1 +fi + +for NEW_FILE in $FILES +do + OLD_FILE=${NEW_FILE/$NEW_PATH/$OLD_PATH} + if [ -f $OLD_FILE ] + then + #echo $NEW_FILE" => "$OLD_FILE + DIFF=`diff -q -w $NEW_FILE $OLD_FILE 2>&1` + if [ "$DIFF" != "" ] + then + # Redo the full diff + echo "$NEW_FILE:" + diff -w $NEW_FILE $OLD_FILE 2>&1 + echo -e "\n\n" + fi + else + echo -e "$NEW_FILE: No corresponding file (expecting $OLD_FILE)\n\n" + fi +done + +echo Done. -- cgit v1.2.3 From c11427c13e85ba6fb210c1f05724c21c8aeb4be3 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 25 Jun 2012 16:53:29 +0200 Subject: Reorganized scaladoc model Since the old model was "interruptible", it was prone to something similar to race conditions -- where the model was creating a template, that template creating was interrupted to creat another template, and so on until a cycle was hit -- then, the loop would be broken by returning the originally not-yet-finished template. Now everything happens in a depth-first order, starting from root, traversing packages and classes all the way to members. The previously interrupting operations are now grouped in two categories: - those that were meant to add entities, like inheriting a class from a template to the other (e.g. trait T { class C }; trait U extends T) => those were moved right after the core model creation - those that were meant to do lookups - like finding the companion object -- those were moved after the model creation and inheritance and are not allowed to create new documentable templates. Now, for the documentable templates we have: DocTemplateImpl - the main documentable template, it represents a Scala template (class, trait, object or package). It may only be created when modelFinished=false by methods in the modelCreation object NoDocTemplateMemberImpl - a non-documented (source not present) template that was inherited. May be used as a member, but does not get its own page NoDocTemplateImpl - a non-documented (source not present) template that may not be used as a member and does not get its own page For model users: you can use anything in the ModelFactory trait at will, but not from the modelCreation object -- that is reserved for the core model creation and using those functions may lead to duplicate templates, invalid links and other ugly problems. --- src/compiler/scala/tools/nsc/doc/DocFactory.scala | 4 +- src/compiler/scala/tools/nsc/doc/Settings.scala | 19 +- src/compiler/scala/tools/nsc/doc/html/Page.scala | 2 +- .../scala/tools/nsc/doc/html/page/Template.scala | 36 +- .../scala/tools/nsc/doc/model/Entity.scala | 53 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 567 +++++++++++++-------- .../doc/model/ModelFactoryImplicitSupport.scala | 46 +- .../scala/tools/nsc/doc/model/TreeFactory.scala | 2 +- .../nsc/doc/model/comment/CommentFactory.scala | 8 +- test/scaladoc/run/SI-5373.scala | 4 +- test/scaladoc/run/package-object.check | 3 +- test/scaladoc/run/package-object.scala | 3 +- 12 files changed, 466 insertions(+), 281 deletions(-) diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index e2e1ddf065..37e3b626e8 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -83,8 +83,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor with model.ModelFactoryImplicitSupport with model.comment.CommentFactory with model.TreeFactory { - override def templateShouldDocument(sym: compiler.Symbol) = - extraTemplatesToDocument(sym) || super.templateShouldDocument(sym) + override def templateShouldDocument(sym: compiler.Symbol, inTpl: TemplateImpl) = + extraTemplatesToDocument(sym) || super.templateShouldDocument(sym, inTpl) } ) diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index d7b61bc129..1cc1f98fba 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -155,15 +155,16 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { * the function result should be a humanly-understandable description of the type class */ val knownTypeClasses: Map[String, String => String] = Map() + - (".scala.package.Numeric" -> ((tparam: String) => tparam + " is a numeric class, such as Int, Long, Float or Double")) + - (".scala.package.Integral" -> ((tparam: String) => tparam + " is an integral numeric class, such as Int or Long")) + - (".scala.package.Fractional" -> ((tparam: String) => tparam + " is a fractional numeric class, such as Float or Double")) + - (".scala.reflect.Manifest" -> ((tparam: String) => tparam + " is accompanied by a Manifest, which is a runtime representation of its type that survives erasure")) + - (".scala.reflect.ClassManifest" -> ((tparam: String) => tparam + " is accompanied by a ClassManifest, which is a runtime representation of its type that survives erasure")) + - (".scala.reflect.OptManifest" -> ((tparam: String) => tparam + " is accompanied by an OptManifest, which can be either a runtime representation of its type or the NoManifest, which means the runtime type is not available")) + - (".scala.reflect.ClassTag" -> ((tparam: String) => tparam + " is accompanied by a ClassTag, which is a runtime representation of its type that survives erasure")) + - (".scala.reflect.AbsTypeTag" -> ((tparam: String) => tparam + " is accompanied by an AbsTypeTag, which is a runtime representation of its type that survives erasure")) + - (".scala.reflect.TypeTag" -> ((tparam: String) => tparam + " is accompanied by a TypeTag, which is a runtime representation of its type that survives erasure")) + // TODO: Bring up to date and test these + ("scala.package.Numeric" -> ((tparam: String) => tparam + " is a numeric class, such as Int, Long, Float or Double")) + + ("scala.package.Integral" -> ((tparam: String) => tparam + " is an integral numeric class, such as Int or Long")) + + ("scala.package.Fractional" -> ((tparam: String) => tparam + " is a fractional numeric class, such as Float or Double")) + + ("scala.reflect.Manifest" -> ((tparam: String) => tparam + " is accompanied by a Manifest, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.ClassManifest" -> ((tparam: String) => tparam + " is accompanied by a ClassManifest, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.OptManifest" -> ((tparam: String) => tparam + " is accompanied by an OptManifest, which can be either a runtime representation of its type or the NoManifest, which means the runtime type is not available")) + + ("scala.reflect.ClassTag" -> ((tparam: String) => tparam + " is accompanied by a ClassTag, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.AbsTypeTag" -> ((tparam: String) => tparam + " is accompanied by an AbsTypeTag, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.TypeTag" -> ((tparam: String) => tparam + " is accompanied by a TypeTag, which is a runtime representation of its type that survives erasure")) /** * Set of classes to exclude from index and diagrams diff --git a/src/compiler/scala/tools/nsc/doc/html/Page.scala b/src/compiler/scala/tools/nsc/doc/html/Page.scala index 72b62dd482..40ae65a37a 100644 --- a/src/compiler/scala/tools/nsc/doc/html/Page.scala +++ b/src/compiler/scala/tools/nsc/doc/html/Page.scala @@ -58,7 +58,7 @@ abstract class Page { def templateToPath(tpl: TemplateEntity): List[String] = { def doName(tpl: TemplateEntity): String = - NameTransformer.encode(tpl.name) + (if (tpl.isObject) "$" else "") + (if (tpl.inPackageObject) "package$$" else "") + NameTransformer.encode(tpl.name) + (if (tpl.isObject) "$" else "") def downPacks(pack: Package): List[String] = if (pack.isRootPackage) Nil else (doName(pack) :: downPacks(pack.inTemplate)) def downInner(nme: String, tpl: TemplateEntity): (String, Package) = { diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 66189a6854..975ff8fb89 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -84,7 +84,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { signature(tpl, true) } - { memberToCommentHtml(tpl, true) } + { memberToCommentHtml(tpl, tpl.inTemplate, true) }
@@ -96,7 +96,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) NodeSeq.Empty else { - if (!tpl.linearization.isEmpty) + if (!tpl.linearizationTemplates.isEmpty)
Inherited
@@ -138,35 +138,35 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { if (constructors.isEmpty) NodeSeq.Empty else

Instance Constructors

-
    { constructors map (memberToHtml(_)) }
+
    { constructors map (memberToHtml(_, tpl)) }
} { if (typeMembers.isEmpty) NodeSeq.Empty else

Type Members

-
    { typeMembers map (memberToHtml(_)) }
+
    { typeMembers map (memberToHtml(_, tpl)) }
} { if (absValueMembers.isEmpty) NodeSeq.Empty else

Abstract Value Members

-
    { absValueMembers map (memberToHtml(_)) }
+
    { absValueMembers map (memberToHtml(_, tpl)) }
} { if (concValueMembers.isEmpty) NodeSeq.Empty else

{ if (absValueMembers.isEmpty) "Value Members" else "Concrete Value Members" }

-
    { concValueMembers map (memberToHtml(_)) }
+
    { concValueMembers map (memberToHtml(_, tpl)) }
} { if (deprValueMembers.isEmpty) NodeSeq.Empty else

Deprecated Value Members

-
    { deprValueMembers map (memberToHtml(_)) }
+
    { deprValueMembers map (memberToHtml(_, tpl)) }
}
@@ -237,12 +237,12 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage tparamsToString(d.typeParams) + paramLists.mkString } - def memberToHtml(mbr: MemberEntity): NodeSeq = { + def memberToHtml(mbr: MemberEntity, inTpl: DocTemplateEntity): NodeSeq = { val defParamsString = mbr match { case d:MemberEntity with Def => defParamsToString(d) case _ => "" } - val memberComment = memberToCommentHtml(mbr, false) + val memberComment = memberToCommentHtml(mbr, inTpl, false)
  • @@ -251,24 +251,24 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
  • } - def memberToCommentHtml(mbr: MemberEntity, isSelf: Boolean): NodeSeq = { + def memberToCommentHtml(mbr: MemberEntity, inTpl: DocTemplateEntity, isSelf: Boolean): NodeSeq = { mbr match { case dte: DocTemplateEntity if isSelf => // comment of class itself -
    { memberToCommentBodyHtml(mbr, isSelf = true) }
    +
    { memberToCommentBodyHtml(mbr, inTpl, isSelf = true) }
    case dte: DocTemplateEntity if mbr.comment.isDefined => // comment of inner, documented class (only short comment, full comment is on the class' own page) memberToInlineCommentHtml(mbr, isSelf) case _ => // comment of non-class member or non-documentented inner class - val commentBody = memberToCommentBodyHtml(mbr, isSelf = false) + val commentBody = memberToCommentBodyHtml(mbr, inTpl, isSelf = false) if (commentBody.isEmpty) NodeSeq.Empty else { val shortComment = memberToShortCommentHtml(mbr, isSelf) - val longComment = memberToUseCaseCommentHtml(mbr, isSelf) ++ memberToCommentBodyHtml(mbr, isSelf) + val longComment = memberToUseCaseCommentHtml(mbr, isSelf) ++ memberToCommentBodyHtml(mbr, inTpl, isSelf) val includedLongComment = if (shortComment.text.trim == longComment.text.trim) NodeSeq.Empty @@ -298,7 +298,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage def memberToInlineCommentHtml(mbr: MemberEntity, isSelf: Boolean): NodeSeq =

    { inlineToHtml(mbr.comment.get.short) }

    - def memberToCommentBodyHtml(mbr: MemberEntity, isSelf: Boolean, isReduced: Boolean = false): NodeSeq = { + def memberToCommentBodyHtml(mbr: MemberEntity, inTpl: DocTemplateEntity, isSelf: Boolean, isReduced: Boolean = false): NodeSeq = { val memberComment = if (mbr.comment.isEmpty) NodeSeq.Empty @@ -383,7 +383,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage }
    - This member is added by an implicit conversion from { typeToHtml(mbr.inTemplate.resultType, true) } to + This member is added by an implicit conversion from { typeToHtml(inTpl.resultType, true) } to { targetType } performed by method { conversionMethod } in { conversionOwner }. { constraintText }
    @@ -404,7 +404,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage val definitionClasses: Seq[scala.xml.Node] = { val inDefTpls = mbr.inDefinitionTemplates - if ((inDefTpls.tail.isEmpty && (inDefTpls.head == mbr.inTemplate)) || isReduced) NodeSeq.Empty + if ((inDefTpls.tail.isEmpty && (inDefTpls.head == inTpl)) || isReduced) NodeSeq.Empty else {
    Definition Classes
    { templatesToHtml(inDefTpls, xml.Text(" → ")) }
    @@ -650,7 +650,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
    {nameHtml} else nameHtml }{ - def tparamsToHtml(mbr: Entity): NodeSeq = mbr match { + def tparamsToHtml(mbr: Any): NodeSeq = mbr match { case hk: HigherKinded => val tpss = hk.typeParams if (tpss.isEmpty) NodeSeq.Empty else { @@ -662,7 +662,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } [{ tparams0(tpss) }] } - case _ => NodeSeq.Empty + case _ => NodeSeq.Empty } tparamsToHtml(mbr) }{ diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 6488847049..8c6fa53fbc 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -24,6 +24,9 @@ import comment._ * - annotations. */ trait Entity { + /** Similar to symbols, so we can track entities */ + def id: Int + /** The name of the entity. Note that the name does not qualify this entity uniquely; use its `qualifiedName` * instead. */ def name : String @@ -48,6 +51,8 @@ trait Entity { /** The annotations attached to this entity, if any. */ def annotations: List[Annotation] + /** The kind of the entity */ + def kind: String } object Entity { @@ -86,9 +91,11 @@ trait TemplateEntity extends Entity { /** Whether this template is a case class. */ def isCaseClass: Boolean + /** Whether or not the template was defined in a package object */ + def inPackageObject: Boolean + /** The self-type of this template, if it differs from the template type. */ def selfType : Option[TypeEntity] - } @@ -177,7 +184,7 @@ object MemberEntity { } /** An entity that is parameterized by types */ -trait HigherKinded extends Entity { +trait HigherKinded { /** The type parameters of this entity. */ def typeParams: List[TypeParam] @@ -187,8 +194,14 @@ trait HigherKinded extends Entity { /** 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 +trait NoDocTemplate extends TemplateEntity { + def kind = "" +} +/** TODO: Document */ +trait NoDocTemplateMemberEntity extends TemplateEntity with MemberEntity { + def kind = "" +} /** A template (class, trait, object or package) for which documentation is available. Only templates for which * a source file is given are documented by Scaladoc. */ @@ -209,9 +222,6 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { /** The direct super-type of this template. */ def parentType: Option[TypeEntity] - @deprecated("Use `linearizationTemplates` and `linearizationTypes` instead", "2.9.0") - def linearization: List[(TemplateEntity, TypeEntity)] - /** All class, trait and object templates which are part of this template's linearization, in lineratization order. * This template's linearization contains all of its direct and indirect super-classes and super-traits. */ def linearizationTemplates: List[TemplateEntity] @@ -254,7 +264,9 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { /** A trait template. */ -trait Trait extends DocTemplateEntity with HigherKinded +trait Trait extends DocTemplateEntity with HigherKinded { + def kind = "trait" +} /** A class template. */ @@ -270,11 +282,14 @@ trait Class extends Trait with HigherKinded { * parameters cannot be curried, the outer list has exactly one element. */ def valueParams: List[List[ValueParam]] + override def kind = "class" } /** An object template. */ -trait Object extends DocTemplateEntity +trait Object extends DocTemplateEntity { + def kind = "object" +} /** A package template. A package is in the universe if it is declared as a package object, or if it @@ -290,6 +305,8 @@ trait Package extends Object { /** All packages that are member of this package. */ def packages: List[Package] + + override def kind = "package" } @@ -323,6 +340,7 @@ trait Def extends NonTemplateMemberEntity with HigherKinded { * Each parameter block is a list of value parameters. */ def valueParams : List[List[ValueParam]] + def kind = "method" } @@ -337,11 +355,14 @@ trait Constructor extends NonTemplateMemberEntity { * element. */ def valueParams : List[List[ValueParam]] + def kind = "constructor" } /** A value (`val`), lazy val (`lazy val`) or variable (`var`) of a template. */ -trait Val extends NonTemplateMemberEntity +trait Val extends NonTemplateMemberEntity { + def kind = "[lazy] value/variable" +} /** An abstract type member of a template. */ @@ -353,6 +374,7 @@ trait AbstractType extends NonTemplateMemberEntity with HigherKinded { /** The upper bound for this abstract type, if it has been defined. */ def hi: Option[TypeEntity] + def kind = "abstract type" } @@ -362,18 +384,14 @@ trait AliasType extends NonTemplateMemberEntity with HigherKinded { /** The type aliased by this type alias. */ def alias: TypeEntity + def kind = "type alias" } /** A parameter to an entity. */ -trait ParameterEntity extends Entity { - - /** Whether this parameter is a type parameter. */ - def isTypeParam: Boolean - - /** Whether this parameter is a value parameter. */ - def isValueParam: Boolean +trait ParameterEntity { + def name: String } @@ -388,7 +406,6 @@ trait TypeParam extends ParameterEntity with HigherKinded { /** The upper bound for this type parameter, if it has been defined. */ def hi: Option[TypeEntity] - } @@ -403,7 +420,6 @@ trait ValueParam extends ParameterEntity { /** Whether this value parameter is implicit. */ def isImplicit: Boolean - } @@ -416,6 +432,7 @@ trait Annotation extends Entity { /** The arguments passed to the constructor of the annotation class. */ def arguments: List[ValueArgument] + def kind = "annotation" } /** A trait that signals the member results from an implicit conversion */ diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 3dd77d47da..9a8df1fd0e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -21,10 +21,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } - import rootMirror.{ RootPackage, EmptyPackage } + import rootMirror.{ RootPackage, RootClass, EmptyPackage } - private var droppedPackages = 0 - def templatesCount = templatesCache.size - droppedPackages + def templatesCount = docTemplatesCache.count(_._2.isDocTemplate) - droppedPackages.size private var modelFinished = false private var universe: Universe = null @@ -45,44 +44,49 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { private lazy val noSubclassCache = Set[Symbol](AnyClass, AnyRefClass, ObjectClass) - /** */ def makeModel: Option[Universe] = { val universe = new Universe { thisUniverse => thisFactory.universe = thisUniverse val settings = thisFactory.settings - private val rootPackageMaybe = makeRootPackage - val rootPackage = rootPackageMaybe.orNull + val rootPackage = modelCreation.createRootPackage } modelFinished = true + // complete the links between model entities, everthing that couldn't have been done before + universe.rootPackage.completeModel + Some(universe) filter (_.rootPackage != null) } - /** */ - protected val templatesCache = - new mutable.LinkedHashMap[Symbol, DocTemplateImpl] - - def findTemplate(query: String): Option[DocTemplateImpl] = { - if (!modelFinished) sys.error("cannot find template in unfinished universe") - templatesCache.values find { tpl => tpl.qualifiedName == query && !tpl.isObject } - } + // state: + var ids = 0 + 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 optimize(str: String): String = if (str.length < 16) str.intern else str /* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */ - abstract class EntityImpl(val sym: Symbol, inTpl: => TemplateImpl) extends Entity { + abstract class EntityImpl(val sym: Symbol, val inTpl: TemplateImpl) extends Entity { + val id = { ids += 1; ids } val name = optimize(sym.nameString) + val universe = thisFactory.universe + + // Debugging: + // assert(id != 36, sym + " " + sym.getClass) + //println("Creating entity #" + id + " [" + kind + " " + qualifiedName + "] for sym " + sym.kindString + " " + sym.ownerChain.reverse.map(_.name).mkString(".")) + def inTemplate: TemplateImpl = inTpl def toRoot: List[EntityImpl] = this :: inTpl.toRoot def qualifiedName = name - val universe = thisFactory.universe def annotations = sym.annotations.map(makeAnnotation) } trait TemplateImpl extends EntityImpl with TemplateEntity { override def qualifiedName: String = - if (inTemplate.isRootPackage) name else optimize(inTemplate.qualifiedName + "." + name) + if (inTemplate == null || inTemplate.isRootPackage) name else optimize(inTemplate.qualifiedName + "." + name) def isPackage = sym.isPackage def isTrait = sym.isTrait def isClass = sym.isClass && !sym.isTrait @@ -90,15 +94,11 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isCaseClass = sym.isCaseClass def isRootPackage = false def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this)) + def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject } - class NoDocTemplateImpl(sym: Symbol, inTpl: => TemplateImpl) extends EntityImpl(sym, inTpl) with TemplateImpl with NoDocTemplate { - def isDocTemplate = false - } - - abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl = null, inTpl: => DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { - lazy val comment = - if (inTpl == null) None else thisFactory.comment(sym, inTpl) + abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { + lazy val comment = if (inTpl != null) thisFactory.comment(sym, inTpl) else None override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot def inDefinitionTemplates = this match { @@ -106,9 +106,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { mb.useCaseOf.get.inDefinitionTemplates case _ => if (inTpl == null) - makeRootPackage.toList + List(makeRootPackage) else - makeTemplate(sym.owner) :: (sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) }) + makeTemplate(sym.owner)::(sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) }) } def visibility = { if (sym.isPrivateLocal) PrivateInInstance() @@ -189,16 +189,40 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def byConversion = if (implConv ne null) Some(implConv) else None } + /** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class + * exists, but should not be documented (either it's not included in the source or it's not visible) + */ + class NoDocTemplateImpl(sym: Symbol, inTpl: TemplateImpl) extends EntityImpl(sym, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplate { + assert(modelFinished) + assert(!(noDocTemplatesCache isDefinedAt sym)) + noDocTemplatesCache += (sym -> this) + + def isDocTemplate = false + } + + /** An inherited template that was not documented in its original owner - example: + * in classpath: trait T { class C } -- T (and implicitly C) are not documented + * 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 + */ + class NoDocTemplateMemberImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplateMemberEntity { + assert(modelFinished) + + def isDocTemplate = false + lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) + } + /** The instantiation of `TemplateImpl` triggers the creation of the following entities: * All ancestors of the template and all non-package members. */ - abstract class DocTemplateImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with DocTemplateEntity { - //if (inTpl != null) println("mbr " + sym + " in " + (inTpl.toRoot map (_.sym)).mkString(" > ")) + abstract class DocTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with DocTemplateEntity { + assert(!modelFinished) + assert(!(docTemplatesCache isDefinedAt sym), sym) + docTemplatesCache += (sym -> this) + if (settings.verbose.value) inform("Creating doc template for " + sym) - templatesCache += (sym -> this) - lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) override def toRoot: List[DocTemplateImpl] = this :: inTpl.toRoot def inSource = if (sym.sourceFile != null && ! sym.isSynthetic) @@ -244,7 +268,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } } - val linearization = linearizationFromSymbol(sym) + lazy val linearization = linearizationFromSymbol(sym) def linearizationTemplates = linearization map { _._1 } def linearizationTypes = linearization map { _._2 } @@ -258,45 +282,65 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } def subClasses = if (subClassesCache == null) Nil else subClassesCache.toList - val conversions = if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil + val conversions: List[ImplicitConversionImpl] = + if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil + + lazy val memberSyms = sym.info.members.filter(s => membersShouldDocument(s, this)) + + var memberSymsLazy = memberSyms.filter(t => templateShouldDocument(t, this) && !inOriginalOnwer(t, this)) + var memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_)) + + var members: List[MemberImpl] = (memberSymsEager.flatMap(makeMember(_, null, this))) ::: + (conversions.flatMap((_.members))) // also take in the members from implicit conversions + + def templates = members collect { case c: DocTemplateEntity => c } + def methods = members collect { case d: Def => d } + def values = members collect { case v: Val => v } + def abstractTypes = members collect { case t: AbstractType => t } + def aliasTypes = members collect { case t: AliasType => t } + + def completeModel: Unit = { + for (member <- members) + member match { + case d: DocTemplateImpl => d.completeModel + case _ => + } - lazy val memberSyms = - // Only this class's constructors are part of its members, inherited constructors are not. - sym.info.members.filter(s => localShouldDocument(s) && (!s.isConstructor || s.owner == sym) && !isPureBridge(sym) ) + members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, inTpl)) - val members = (memberSyms.flatMap(makeMember(_, null, this))) ::: - (conversions.flatMap((_.members))) // also take in the members from implicit conversions + // compute linearization to register subclasses + linearization + } - val templates = members collect { case c: DocTemplateEntity => c } - val methods = members collect { case d: Def => d } - val values = members collect { case v: Val => v } - val abstractTypes = members collect { case t: AbstractType => t } - val aliasTypes = members collect { case t: AliasType => t } override def isTemplate = true + lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) def isDocTemplate = true def companion = sym.companionSymbol match { case NoSymbol => None case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) => - Some(makeDocTemplate(comSym, inTpl)) + makeTemplate(comSym) match { + case d: DocTemplateImpl => Some(d) + case _ => None + } case _ => None } } - abstract class PackageImpl(sym: Symbol, inTpl: => PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package { + abstract class PackageImpl(sym: Symbol, inTpl: PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package { override def inTemplate = inTpl override def toRoot: List[PackageImpl] = this :: inTpl.toRoot - override val linearization = { + override lazy val linearization = { val symbol = sym.info.members.find { s => s.isPackageObject } getOrElse sym linearizationFromSymbol(symbol) } - val packages = members collect { case p: Package => p } + def packages = members collect { case p: PackageImpl if !(droppedPackages contains p) => p } } abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity - abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity { + abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity { override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name) lazy val definitionName = if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + "#" + name) @@ -305,7 +349,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isBridge = sym.isBridge } - abstract class NonTemplateParamMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl) extends NonTemplateMemberImpl(sym, implConv, inTpl) { + abstract class NonTemplateParamMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends NonTemplateMemberImpl(sym, implConv, inTpl) { def valueParams = { val info = if (implConv eq null) sym.info else implConv.toType memberInfo sym info.paramss map { ps => (ps.zipWithIndex) map { case (p, i) => @@ -314,26 +358,30 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } } - abstract class ParameterImpl(sym: Symbol, inTpl: => TemplateImpl) extends EntityImpl(sym, inTpl) with ParameterEntity { - override def inTemplate = inTpl + abstract class ParameterImpl(val sym: Symbol, val inTpl: TemplateImpl) extends ParameterEntity { + val name = optimize(sym.nameString) } - private trait TypeBoundsImpl extends EntityImpl { + private trait TypeBoundsImpl { + def sym: Symbol + def inTpl: TemplateImpl def lo = sym.info.bounds match { case TypeBounds(lo, hi) if lo.typeSymbol != NothingClass => - Some(makeTypeInTemplateContext(appliedType(lo, sym.info.typeParams map {_.tpe}), inTemplate, sym)) + Some(makeTypeInTemplateContext(appliedType(lo, sym.info.typeParams map {_.tpe}), inTpl, sym)) case _ => None } def hi = sym.info.bounds match { case TypeBounds(lo, hi) if hi.typeSymbol != AnyClass => - Some(makeTypeInTemplateContext(appliedType(hi, sym.info.typeParams map {_.tpe}), inTemplate, sym)) + Some(makeTypeInTemplateContext(appliedType(hi, sym.info.typeParams map {_.tpe}), inTpl, sym)) case _ => None } } - trait HigherKindedImpl extends EntityImpl with HigherKinded { + trait HigherKindedImpl extends HigherKinded { + def sym: Symbol + def inTpl: TemplateImpl def typeParams = - sym.typeParams map (makeTypeParam(_, inTemplate)) + sym.typeParams map (makeTypeParam(_, inTpl)) } /* ============== MAKER METHODS ============== */ @@ -352,145 +400,133 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { aSym } - def makeRootPackage: Option[PackageImpl] = - makePackage(RootPackage, null) + /** + * These are all model construction methods. Please do not use them directly, they are calling each other recursively + * starting from makeModel. On the other hand, makeTemplate, makeAnnotation, makeMember, makeType should only be used + * after the model was created (modelFinished=true) otherwise assertions will start failing. + */ + object modelCreation { - /** Creates a package entity for the given symbol or returns `None` if the symbol does not denote a package that - * contains at least one ''documentable'' class, trait or object. Creating a package entity */ - def makePackage(aSym: Symbol, inTpl: => PackageImpl): Option[PackageImpl] = { - val bSym = normalizeTemplate(aSym) - if (templatesCache isDefinedAt (bSym)) - Some(templatesCache(bSym) match {case p: PackageImpl => p}) - else { - val pack = - if (bSym == RootPackage) - new RootPackageImpl(bSym) { - override lazy val comment = - if(settings.docRootContent.isDefault) None - else { - import Streamable._ - Path(settings.docRootContent.value) match { - case f : File => { - val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition)) - Some(rootComment) - } - case _ => None - } - } - override val name = "root" - override def inTemplate = this - override def toRoot = this :: Nil - override def qualifiedName = "_root_" - override def inheritedFrom = Nil - override def isRootPackage = true - override lazy val memberSyms = - (bSym.info.members ++ EmptyPackage.info.members) filter { s => - s != EmptyPackage && s != RootPackage - } - } - else - new PackageImpl(bSym, inTpl) {} - if (pack.templates.isEmpty) { - droppedPackages += 1 - None - } - else Some(pack) + def createRootPackage: PackageImpl = docTemplatesCache.get(RootPackage) match { + case Some(root: PackageImpl) => root + case _ => modelCreation.createTemplate(RootPackage, null).asInstanceOf[PackageImpl] } - } - - /** */ - def makeTemplate(aSym: Symbol): TemplateImpl = { - val bSym = normalizeTemplate(aSym) - if (bSym == RootPackage) - makeRootPackage.get - else if (bSym.isPackage) - makeTemplate(bSym.owner) match { - case inPkg: PackageImpl => makePackage(bSym, inPkg) getOrElse (new NoDocTemplateImpl(bSym, inPkg)) - case inNoDocTpl: NoDocTemplateImpl => new NoDocTemplateImpl(bSym, inNoDocTpl) - case _ => throw new Error("'" + bSym + "' must be in a package") - } - else if (templateShouldDocument(bSym)) - makeTemplate(bSym.owner) match { - case inDTpl: DocTemplateImpl => makeDocTemplate(bSym, inDTpl) - case inNoDocTpl: NoDocTemplateImpl => new NoDocTemplateImpl(bSym, inNoDocTpl) - case _ => throw new Error("'" + bSym + "' must be in documentable template") - } - else - new NoDocTemplateImpl(bSym, makeTemplate(bSym.owner)) - } - - /** */ - def makeDocTemplate(aSym: Symbol, inTpl: => DocTemplateImpl): DocTemplateImpl = { - val bSym = normalizeTemplate(aSym) - val minimumInTpl = - if (bSym.owner != inTpl.sym) - makeTemplate(aSym.owner) match { - case inDTpl: DocTemplateImpl => inDTpl - case inNDTpl => throw new Error("'" + bSym + "' is owned by '" + inNDTpl + "' which is not documented") + /** + * Create a template, either a package, class, trait or object + */ + def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { + // don't call this after the model finished! + assert(!modelFinished) + + def createRootPackageComment: Option[Comment] = + if(settings.docRootContent.isDefault) None + else { + import Streamable._ + Path(settings.docRootContent.value) match { + case f : File => { + val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition)) + Some(rootComment) + } + case _ => None + } } - else - inTpl - if (templatesCache isDefinedAt (bSym)) - templatesCache(bSym) - else if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) - new DocTemplateImpl(bSym, minimumInTpl) with Object - else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) - new DocTemplateImpl(bSym, minimumInTpl) with Trait - else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass)) - new DocTemplateImpl(bSym, minimumInTpl) with Class { - def valueParams = - // we don't want params on a class (non case class) signature - if (isCaseClass) List(sym.constrParamAccessors map (makeValueParam(_, this))) - else List.empty - val constructors = - members collect { case d: Constructor => d } - def primaryConstructor = constructors find { _.isPrimary } + + def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { + if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) + new DocTemplateImpl(bSym, inTpl) with Object + else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) + new DocTemplateImpl(bSym, inTpl) with Trait + else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass)) + new DocTemplateImpl(bSym, inTpl) with Class { + def valueParams = + // we don't want params on a class (non case class) signature + if (isCaseClass) List(sym.constrParamAccessors map (makeValueParam(_, this))) + else List.empty + val constructors = + members collect { case d: Constructor => d } + def primaryConstructor = constructors find { _.isPrimary } + } + else + sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a documentable template") } - else - throw new Error("'" + bSym + "' that isn't a class, trait or object cannot be built as a documentable template") - } - /** */ - def makeAnnotation(annot: AnnotationInfo): Annotation = { - val aSym = annot.symbol - new EntityImpl(aSym, makeTemplate(aSym.owner)) with Annotation { - lazy val annotationClass = - makeTemplate(annot.symbol) - val arguments = { // lazy - def noParams = annot.args map { _ => None } - val params: List[Option[ValueParam]] = annotationClass match { - case aClass: Class => - (aClass.primaryConstructor map { _.valueParams.head }) match { - case Some(vps) => vps map { Some(_) } - case None => noParams + val bSym = normalizeTemplate(aSym) + if (docTemplatesCache isDefinedAt bSym) + return docTemplatesCache(bSym) + + /* Three cases of templates: + * (1) root package -- special cased for bootstrapping + * (2) package + * (3) class/object/trait + */ + if (bSym == RootPackage) // (1) + new RootPackageImpl(bSym) { + override lazy val comment = createRootPackageComment + override val name = "root" + override def inTemplate = this + override def toRoot = this :: Nil + override def qualifiedName = "_root_" + override def inheritedFrom = Nil + override def isRootPackage = true + override lazy val memberSyms = + (bSym.info.members ++ EmptyPackage.info.members) filter { s => + s != EmptyPackage && s != RootPackage } - case _ => noParams } - assert(params.length == annot.args.length) - (params zip annot.args) flatMap { case (param, arg) => - makeTree(arg) match { - case Some(tree) => - Some(new ValueArgument { - def parameter = param - def value = tree - }) - case None => None - } + else if (bSym.isPackage) // (2) + inTpl match { + case inPkg: PackageImpl => + val pack = new PackageImpl(bSym, inPkg) {} + if (pack.templates.isEmpty && pack.memberSymsLazy.isEmpty) + droppedPackages += pack + pack + case _ => + sys.error("'" + bSym + "' must be in a package") } + else { + // no class inheritance at this point + assert(inOriginalOnwer(bSym, inTpl)) + createDocTemplate(bSym, inTpl) } } + + /** + * After the model is completed, no more DocTemplateEntities are created. + * Therefore any symbol that still appears is: + * - NoDocTemplateMemberEntity (created here) + * - NoDocTemplateEntity (created in makeTemplate) + */ + def createLazyTemplateMember(aSym: Symbol, inTpl: DocTemplateImpl): MemberImpl = { + assert(modelFinished) + val bSym = normalizeTemplate(aSym) + + if (docTemplatesCache isDefinedAt bSym) + docTemplatesCache(bSym) + else + docTemplatesCache.get(bSym.owner) match { + case Some(inTpl) => + val mbrs = inTpl.members.collect({ case mbr: MemberImpl if mbr.sym == bSym => mbr }) + assert(mbrs.length == 1) + mbrs.head + case _ => + // move the class completely to the new location + new NoDocTemplateMemberImpl(aSym, inTpl) + } + } } - /** */ + /** Get the root package */ + def makeRootPackage: PackageImpl = docTemplatesCache(RootPackage).asInstanceOf[PackageImpl] + // TODO: Should be able to override the type - def makeMember(aSym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl): List[MemberImpl] = { + def makeMember(aSym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl): List[MemberImpl] = { def makeMember0(bSym: Symbol, _useCaseOf: Option[MemberImpl]): Option[MemberImpl] = { if (bSym.isGetter && bSym.isLazy) Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { override lazy val comment = // The analyser does not duplicate the lazy val's DocDef when it introduces its accessor. - thisFactory.comment(bSym.accessed, inTpl) // This hack should be removed after analyser is fixed. + thisFactory.comment(bSym.accessed, inTpl.asInstanceOf[DocTemplateImpl]) // This hack should be removed after analyser is fixed. override def isLazyVal = true override def useCaseOf = _useCaseOf }) @@ -538,10 +574,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) override def useCaseOf = _useCaseOf }) - else if (bSym.isPackage) - inTpl match { case inPkg: PackageImpl => makePackage(bSym, inPkg) } - else if ((bSym.isClass || bSym.isModule || bSym == AnyRefClass) && templateShouldDocument(bSym)) - Some(makeDocTemplate(bSym, inTpl)) + else if (bSym.isPackage && !modelFinished) + inTpl match { + case inPkg: PackageImpl => modelCreation.createTemplate(bSym, inTpl) match { + case p: PackageImpl if droppedPackages contains p => None + case p: PackageImpl => Some(p) + case _ => sys.error("'" + bSym + "' must be a package") + } + case _ => + sys.error("'" + bSym + "' must be in a package") + } + else if (!modelFinished && templateShouldDocument(bSym, inTpl) && inOriginalOnwer(bSym, inTpl)) + Some(modelCreation.createTemplate(bSym, inTpl)) else None } @@ -561,14 +605,78 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // Use cases replace the original definitions - SI-5054 allSyms flatMap { makeMember0(_, member) } } + } + + def findMember(aSym: Symbol, inTpl: DocTemplateImpl): Option[MemberImpl] = { + val tplSym = normalizeTemplate(aSym.owner) + inTpl.members.find(_.sym == aSym) + } + + def findTemplate(query: String): Option[DocTemplateImpl] = { + assert(modelFinished) + docTemplatesCache.values find { (tpl: TemplateImpl) => tpl.qualifiedName == query && !tpl.isObject } + } + + def findTemplateMaybe(aSym: Symbol): Option[DocTemplateImpl] = { + assert(modelFinished) + docTemplatesCache.get(normalizeTemplate(aSym)) + } + + def makeTemplate(aSym: Symbol): TemplateImpl = { + assert(modelFinished) + def makeNoDocTemplate(aSym: Symbol, inTpl: TemplateImpl): NoDocTemplateImpl = { + val bSym = normalizeTemplate(aSym) + noDocTemplatesCache.get(bSym) match { + case Some(noDocTpl) => noDocTpl + case None => new NoDocTemplateImpl(bSym, inTpl) + } + } + + findTemplateMaybe(aSym) match { + case Some(dtpl) => + dtpl + case None => + val bSym = normalizeTemplate(aSym) + makeNoDocTemplate(bSym, makeTemplate(bSym.owner)) + } + } + + + /** */ + def makeAnnotation(annot: AnnotationInfo): Annotation = { + val aSym = annot.symbol + new EntityImpl(aSym, makeTemplate(aSym.owner)) with Annotation { + lazy val annotationClass = + makeTemplate(annot.symbol) + val arguments = { // lazy + def noParams = annot.args map { _ => None } + val params: List[Option[ValueParam]] = annotationClass match { + case aClass: Class => + (aClass.primaryConstructor map { _.valueParams.head }) match { + case Some(vps) => vps map { Some(_) } + case None => noParams + } + case _ => noParams + } + assert(params.length == annot.args.length) + (params zip annot.args) flatMap { case (param, arg) => + makeTree(arg) match { + case Some(tree) => + Some(new ValueArgument { + def parameter = param + def value = tree + }) + case None => None + } + } + } + } } /** */ - def makeTypeParam(aSym: Symbol, inTpl: => TemplateImpl): TypeParam = + def makeTypeParam(aSym: Symbol, inTpl: TemplateImpl): TypeParam = new ParameterImpl(aSym, inTpl) with TypeBoundsImpl with HigherKindedImpl with TypeParam { - def isTypeParam = true - def isValueParam = false def variance: String = { if (sym hasFlag Flags.COVARIANT) "+" else if (sym hasFlag Flags.CONTRAVARIANT) "-" @@ -577,16 +685,15 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /** */ - def makeValueParam(aSym: Symbol, inTpl: => DocTemplateImpl): ValueParam = { + def makeValueParam(aSym: Symbol, inTpl: DocTemplateImpl): ValueParam = { makeValueParam(aSym, inTpl, aSym.nameString) } + /** */ - def makeValueParam(aSym: Symbol, inTpl: => DocTemplateImpl, newName: String): ValueParam = + def makeValueParam(aSym: Symbol, inTpl: DocTemplateImpl, newName: String): ValueParam = new ParameterImpl(aSym, inTpl) with ValueParam { override val name = newName - def isTypeParam = false - def isValueParam = true def defaultValue = if (aSym.hasDefault) { // units.filter should return only one element @@ -601,12 +708,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } else None def resultType = - makeTypeInTemplateContext(sym.tpe, inTpl, sym) + makeTypeInTemplateContext(aSym.tpe, inTpl, aSym) def isImplicit = aSym.isImplicit } /** */ - def makeTypeInTemplateContext(aType: Type, inTpl: => TemplateImpl, dclSym: Symbol): TypeEntity = { + def makeTypeInTemplateContext(aType: Type, inTpl: TemplateImpl, dclSym: Symbol): TypeEntity = { def ownerTpl(sym: Symbol): Symbol = if (sym.isClass || sym.isModule || sym == NoSymbol) sym else ownerTpl(sym.owner) val tpe = @@ -620,10 +727,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /** */ - def makeType(aType: Type, inTpl: => TemplateImpl): TypeEntity = { + def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { def templatePackage = closestPackage(inTpl.sym) - new TypeEntity { + def createTypeEntity = new TypeEntity { private val nameBuffer = new StringBuilder private var refBuffer = new immutable.TreeMap[Int, (TemplateEntity, Int)] private def appendTypes0(types: List[Type], sep: String): Unit = types match { @@ -719,23 +826,81 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val refEntity = refBuffer val name = optimize(nameBuffer.toString) } - } - def templateShouldDocument(aSym: Symbol): Boolean = { - // TODO: document sourceless entities (e.g., Any, etc), based on a new Setting to be added - (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && - ( aSym.owner == NoSymbol || templateShouldDocument(aSym.owner) ) && !isEmptyJavaObject(aSym) + if (aType.isTrivial) + typeCache.get(aType) match { + case Some(typeEntity) => typeEntity + case None => + val typeEntity = createTypeEntity + typeCache += aType -> typeEntity + typeEntity + } + else + createTypeEntity } - def isEmptyJavaObject(aSym: Symbol): Boolean = { - def hasMembers = aSym.info.members.exists(s => localShouldDocument(s) && (!s.isConstructor || s.owner == aSym)) - aSym.isModule && aSym.isJavaDefined && !hasMembers - } + def normalizeOwner(aSym: Symbol): Symbol = + /* + * Okay, here's the explanation of what happens. The code: + * + * package foo { + * object `package` { + * class Bar + * } + * } + * + * will yield this Symbol structure: + * + * +---------------+ +--------------------------+ + * | package foo#1 ----(1)---> module class foo#2 | + * +---------------+ | +----------------------+ | +-------------------------+ + * | | package object foo#3 ------(1)---> module class package#4 | + * | +----------------------+ | | +---------------------+ | + * +--------------------------+ | | class package$Bar#5 | | + * | +---------------------+ | + * +-------------------------+ + * (1) sourceModule + * (2) you get out of owners with .owner + */ + normalizeTemplate(aSym) match { + case bSym if bSym.isPackageObject => + normalizeOwner(bSym.owner) + case bSym => + bSym + } - def localShouldDocument(aSym: Symbol): Boolean = { + def inOriginalOnwer(aSym: Symbol, inTpl: TemplateImpl): Boolean = + normalizeOwner(aSym.owner) == normalizeOwner(inTpl.sym) + + def templateShouldDocument(aSym: Symbol, inTpl: TemplateImpl): Boolean = + (aSym.isClass || aSym.isModule || aSym == AnyRefClass) && + localShouldDocument(aSym) && + !isEmptyJavaObject(aSym) && + // either it's inside the original owner or we can document it later: + (!inOriginalOnwer(aSym, inTpl) || (aSym.isPackageClass || (aSym.sourceFile != null))) + + def membersShouldDocument(sym: Symbol, inTpl: TemplateImpl) = + // pruning modules that shouldn't be documented + // Why Symbol.isInitialized? Well, because we need to avoid exploring all the space available to scaladoc + // from the classpath -- scaladoc is a hog, it will explore everything starting from the root package unless we + // somehow prune the tree. And isInitialized is a good heuristic for prunning -- if the package was not explored + // during typer and refchecks, it's not necessary for the current application and there's no need to explore it. + (!sym.isModule || sym.moduleClass.isInitialized) && + // documenting only public and protected members + localShouldDocument(sym) && + // Only this class's constructors are part of its members, inherited constructors are not. + (!sym.isConstructor || sym.owner == inTpl.sym) && + // If the @bridge annotation overrides a normal member, show it + !isPureBridge(sym) + + def isEmptyJavaObject(aSym: Symbol): Boolean = + aSym.isModule && aSym.isJavaDefined && + aSym.info.members.exists(s => localShouldDocument(s) && (!s.isConstructor || s.owner == aSym)) + + def localShouldDocument(aSym: Symbol): Boolean = !aSym.isPrivate && (aSym.isProtected || aSym.privateWithin == NoSymbol) && !aSym.isSynthetic - } /** Filter '@bridge' methods only if *they don't override non-bridge methods*. See SI-5373 for details */ def isPureBridge(sym: Symbol) = sym.isBridge && sym.allOverriddenSymbols.forall(_.isBridge) } + diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index c3525037cd..2a0fcea0ea 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -58,6 +58,7 @@ trait ModelFactoryImplicitSupport { import global._ import global.analyzer._ import global.definitions._ + import rootMirror.{RootPackage, RootClass, EmptyPackage, EmptyPackageClass} import settings.hardcoded // debugging: @@ -96,13 +97,7 @@ trait ModelFactoryImplicitSupport { def targetType: TypeEntity = makeType(toType, inTpl) - def convertorOwner: TemplateEntity = - if (convSym != NoSymbol) - makeTemplate(convSym.owner) - else { - error("Scaladoc implicits: Implicit conversion from " + sym.tpe + " to " + toType + " done by " + convSym + " = NoSymbol!") - makeRootPackage.get // surely the root package was created :) - } + def convertorOwner: TemplateEntity = makeTemplate(convSym.owner) def convertorMethod: Either[MemberEntity, String] = { var convertor: MemberEntity = null @@ -122,11 +117,11 @@ trait ModelFactoryImplicitSupport { def conversionShortName = convSym.nameString - def conversionQualifiedName = convertorOwner.qualifiedName + "." + convSym.nameString + def conversionQualifiedName = makeQualifiedName(convSym) lazy val constraints: List[Constraint] = constrs - val members: List[MemberEntity] = { + val members: List[MemberImpl] = { // Obtain the members inherited by the implicit conversion var memberSyms = toType.members.filter(implicitShouldDocument(_)) val existingMembers = sym.info.members @@ -162,7 +157,7 @@ trait ModelFactoryImplicitSupport { * default Scala imports (Predef._ for example) and the companion object of the current class, if one exists. In the * future we might want to extend this to more complex scopes. */ - def makeImplicitConversions(sym: Symbol, inTpl: => DocTemplateImpl): List[ImplicitConversion] = + def makeImplicitConversions(sym: Symbol, inTpl: => DocTemplateImpl): List[ImplicitConversionImpl] = // Nothing and Null are somewhat special -- they can be transformed by any implicit conversion available in scope. // But we don't want that, so we'll simply refuse to find implicit conversions on for Nothing and Null if (!(sym.isClass || sym.isTrait || sym == AnyRefClass) || sym == NothingClass || sym == NullClass) Nil @@ -218,7 +213,7 @@ trait ModelFactoryImplicitSupport { * - we also need to transform implicit parameters in the view's signature into constraints, such that Numeric[T4] * appears as a constraint */ - def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: => DocTemplateImpl): List[ImplicitConversion] = + def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: => DocTemplateImpl): List[ImplicitConversionImpl] = if (result.tree == EmptyTree) Nil else { // `result` will contain the type of the view (= implicit conversion method) @@ -280,7 +275,7 @@ trait ModelFactoryImplicitSupport { types.flatMap((tpe:Type) => { // TODO: Before creating constraints, map typeVarToOriginOrWildcard on the implicitTypes val implType = typeVarToOriginOrWildcard(tpe) - val qualifiedName = implType.typeSymbol.ownerChain.reverse.map(_.nameString).mkString(".") + val qualifiedName = makeQualifiedName(implType.typeSymbol) var available: Option[Boolean] = None @@ -333,20 +328,20 @@ trait ModelFactoryImplicitSupport { case Some(explanation) => List(new KnownTypeClassConstraint { val typeParamName = targ.nameString - val typeExplanation = explanation - val typeClassEntity = makeTemplate(sym) - val implicitType: TypeEntity = makeType(implType, inTpl) + lazy val typeExplanation = explanation + lazy val typeClassEntity = makeTemplate(sym) + lazy val implicitType: TypeEntity = makeType(implType, inTpl) }) case None => List(new TypeClassConstraint { val typeParamName = targ.nameString - val typeClassEntity = makeTemplate(sym) - val implicitType: TypeEntity = makeType(implType, inTpl) + lazy val typeClassEntity = makeTemplate(sym) + lazy val implicitType: TypeEntity = makeType(implType, inTpl) }) } case _ => List(new ImplicitInScopeConstraint{ - val implicitType: TypeEntity = makeType(implType, inTpl) + lazy val implicitType: TypeEntity = makeType(implType, inTpl) }) } } @@ -372,23 +367,23 @@ trait ModelFactoryImplicitSupport { case (List(lo), List(up)) if (lo == up) => List(new EqualTypeParamConstraint { val typeParamName = tparam.nameString - val rhs = makeType(lo, inTpl) + lazy val rhs = makeType(lo, inTpl) }) case (List(lo), List(up)) => List(new BoundedTypeParamConstraint { val typeParamName = tparam.nameString - val lowerBound = makeType(lo, inTpl) - val upperBound = makeType(up, inTpl) + lazy val lowerBound = makeType(lo, inTpl) + lazy val upperBound = makeType(up, inTpl) }) case (List(lo), Nil) => List(new LowerBoundedTypeParamConstraint { val typeParamName = tparam.nameString - val lowerBound = makeType(lo, inTpl) + lazy val lowerBound = makeType(lo, inTpl) }) case (Nil, List(up)) => List(new UpperBoundedTypeParamConstraint { val typeParamName = tparam.nameString - val upperBound = makeType(up, inTpl) + lazy val upperBound = makeType(up, inTpl) }) case other => // this is likely an error on the lub/glb side @@ -399,6 +394,11 @@ trait ModelFactoryImplicitSupport { } } + def makeQualifiedName(sym: Symbol): String = { + val remove = Set[Symbol](RootPackage, RootClass, EmptyPackage, EmptyPackageClass) + sym.ownerChain.filterNot(remove.contains(_)).reverse.map(_.nameString).mkString(".") + } + /** * uniteConstraints takes a TypeConstraint instance and simplifies the constraints inside * diff --git a/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala b/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala index fe586c4996..bd7534ded4 100755 --- a/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala @@ -52,7 +52,7 @@ trait TreeFactory { thisTreeFactory: ModelFactory with TreeFactory => if (asym.isSetter) asym = asym.getter(asym.owner) makeTemplate(asym.owner) match { case docTmpl: DocTemplateImpl => - val mbrs: List[MemberImpl] = makeMember(asym, null, docTmpl) + val mbrs: Option[MemberImpl] = findMember(asym, docTmpl) mbrs foreach { mbr => refs += ((start, (mbr,end))) } case _ => } diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala index 996223b9f9..c749d30267 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -733,8 +733,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => nextChar() } - /** - * Eliminates the (common) leading spaces in all lines, based on the first line + /** + * Eliminates the (common) leading spaces in all lines, based on the first line * For indented pieces of code, it reduces the indent to the least whitespace prefix: * {{{ * indented example @@ -757,11 +757,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => while (index < code.length) { code(index) match { case ' ' => - if (wsArea) + if (wsArea) crtSkip += 1 case c => wsArea = (c == '\n') - maxSkip = if (firstLine || emptyLine) maxSkip else if (maxSkip <= crtSkip) maxSkip else crtSkip + maxSkip = if (firstLine || emptyLine) maxSkip else if (maxSkip <= crtSkip) maxSkip else crtSkip crtSkip = if (c == '\n') 0 else crtSkip firstLine = if (c == '\n') false else firstLine emptyLine = if (c == '\n') true else false diff --git a/test/scaladoc/run/SI-5373.scala b/test/scaladoc/run/SI-5373.scala index 0062abbb2a..65cf8baff5 100644 --- a/test/scaladoc/run/SI-5373.scala +++ b/test/scaladoc/run/SI-5373.scala @@ -12,12 +12,12 @@ object Test extends ScaladocModelTest { def foo = () } - trait B { + trait B extends A { @bridge() def foo = () } - class C extends A with B + class C extends B } """ diff --git a/test/scaladoc/run/package-object.check b/test/scaladoc/run/package-object.check index 4297847e73..01dbcc682f 100644 --- a/test/scaladoc/run/package-object.check +++ b/test/scaladoc/run/package-object.check @@ -1,2 +1,3 @@ -List((test.B,B), (test.A,A), (scala.AnyRef,AnyRef), (scala.Any,Any)) +List(test.B, test.A, scala.AnyRef, scala.Any) +List(B, A, AnyRef, Any) Done. diff --git a/test/scaladoc/run/package-object.scala b/test/scaladoc/run/package-object.scala index fd36a8df7b..5fb5a4ddf1 100644 --- a/test/scaladoc/run/package-object.scala +++ b/test/scaladoc/run/package-object.scala @@ -9,7 +9,8 @@ object Test extends ScaladocModelTest { import access._ val p = root._package("test") - println(p.linearization) + println(p.linearizationTemplates) + println(p.linearizationTypes) } } -- cgit v1.2.3 From fba65513d1195b162f958a5c25124958d6e7ea52 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 13 Jun 2012 16:35:58 +0200 Subject: Scaladoc class diagrams part 1 This commit contains model changes required for adding class diagrams to scaladoc. It also contains an improved implicit shadowing computation, which hides the shadowed implicitly inherited members from the main view and gives instructions on how to access them. This is joint work with Damien Obrist (@damienobrist) on supporting diagram generation in scaladoc, as part of Damien's semester project in the LAMP laborarory at EPFL. The full history is located at: https://github.com/damienobrist/scala/tree/feature/diagrams-dev Commit summary: - diagrams model - diagram settings (Settings.scala, ScalaDoc.scala) - diagram model object (Entity.scala, Diagram.scala) - model: tracking direct superclasses and subclasses, implicit conversions from and to (ModelFactory.scala) - diagram object computation (DiagramFactory.scala, DocFactory.scala) - capacity to filter diagrams (CommentFactory.scala, DiagramDirectiveParser.scala) - diagram statistics object (DiagramStats.scala) - delayed link evaluation (Body.scala, Comment.scala) - tests - improved implicits shadowing information - model shadowing computation (ModelFactoryImplicitSupport.scala, Entity.scala) - html generation for shadowing information (Template.scala) - tests Also fixes an issue reported by @dragos, where single-line comment expansion would lead to the comment disappearing. Review by @kzys, @pedrofurla. Adapted to the new model and fixed a couple of problems: - duplicate implicit conversions in StringAdd/StringFormat - incorrect implicit conversion signature (from X to X) Conflicts: src/compiler/scala/tools/nsc/doc/Settings.scala src/compiler/scala/tools/nsc/doc/html/page/Template.scala src/compiler/scala/tools/nsc/doc/model/Entity.scala src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala --- src/compiler/scala/tools/nsc/ScalaDoc.scala | 3 +- src/compiler/scala/tools/nsc/doc/DocFactory.scala | 6 +- src/compiler/scala/tools/nsc/doc/Settings.scala | 58 ++++- .../scala/tools/nsc/doc/html/HtmlPage.scala | 16 +- src/compiler/scala/tools/nsc/doc/html/Page.scala | 14 -- .../scala/tools/nsc/doc/html/page/Index.scala | 2 +- .../tools/nsc/doc/html/page/IndexScript.scala | 2 +- .../scala/tools/nsc/doc/html/page/Template.scala | 81 ++++++- .../nsc/doc/html/page/diagram/DiagramStats.scala | 58 +++++ .../tools/nsc/doc/html/resource/lib/template.css | 6 +- .../scala/tools/nsc/doc/model/Entity.scala | 80 +++++-- .../scala/tools/nsc/doc/model/ModelFactory.scala | 117 +++++++-- .../doc/model/ModelFactoryImplicitSupport.scala | 265 ++++++++++++++------- .../scala/tools/nsc/doc/model/comment/Body.scala | 2 +- .../tools/nsc/doc/model/comment/Comment.scala | 6 + .../nsc/doc/model/comment/CommentFactory.scala | 132 +++++----- .../tools/nsc/doc/model/diagram/Diagram.scala | 143 +++++++++++ .../doc/model/diagram/DiagramDirectiveParser.scala | 248 +++++++++++++++++++ .../nsc/doc/model/diagram/DiagramFactory.scala | 197 +++++++++++++++ .../scala/tools/partest/ScaladocModelTest.scala | 4 +- .../resources/implicits-ambiguating-res.scala | 72 ++++++ test/scaladoc/resources/implicits-base-res.scala | 16 +- .../resources/implicits-elimination-res.scala | 6 +- test/scaladoc/run/diagrams-base.check | 1 + test/scaladoc/run/diagrams-base.scala | 73 ++++++ test/scaladoc/run/diagrams-determinism.check | 1 + test/scaladoc/run/diagrams-determinism.scala | 67 ++++++ test/scaladoc/run/diagrams-filtering.check | 1 + test/scaladoc/run/diagrams-filtering.scala | 93 ++++++++ test/scaladoc/run/implicits-ambiguating.check | 1 + test/scaladoc/run/implicits-ambiguating.scala | 114 +++++++++ test/scaladoc/run/implicits-base.scala | 39 ++- test/scaladoc/run/implicits-elimination.check | 1 - test/scaladoc/run/implicits-elimination.scala | 23 -- test/scaladoc/run/implicits-shadowing.scala | 35 +-- test/scaladoc/run/implicits-var-exp.check | 1 + test/scaladoc/run/implicits-var-exp.scala | 43 ++++ test/scaladoc/scalacheck/CommentFactoryTest.scala | 6 +- 38 files changed, 1766 insertions(+), 267 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala create mode 100644 src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala create mode 100644 src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala create mode 100644 src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala create mode 100644 test/scaladoc/resources/implicits-ambiguating-res.scala create mode 100644 test/scaladoc/run/diagrams-base.check create mode 100644 test/scaladoc/run/diagrams-base.scala create mode 100644 test/scaladoc/run/diagrams-determinism.check create mode 100644 test/scaladoc/run/diagrams-determinism.scala create mode 100644 test/scaladoc/run/diagrams-filtering.check create mode 100644 test/scaladoc/run/diagrams-filtering.scala create mode 100644 test/scaladoc/run/implicits-ambiguating.check create mode 100644 test/scaladoc/run/implicits-ambiguating.scala delete mode 100644 test/scaladoc/run/implicits-elimination.check delete mode 100644 test/scaladoc/run/implicits-elimination.scala create mode 100644 test/scaladoc/run/implicits-var-exp.check create mode 100644 test/scaladoc/run/implicits-var-exp.scala diff --git a/src/compiler/scala/tools/nsc/ScalaDoc.scala b/src/compiler/scala/tools/nsc/ScalaDoc.scala index 5a4b4172c6..c6fdb4b891 100644 --- a/src/compiler/scala/tools/nsc/ScalaDoc.scala +++ b/src/compiler/scala/tools/nsc/ScalaDoc.scala @@ -20,7 +20,8 @@ class ScalaDoc { def process(args: Array[String]): Boolean = { var reporter: ConsoleReporter = null - val docSettings = new doc.Settings(msg => reporter.error(FakePos("scaladoc"), msg + "\n scaladoc -help gives more information")) + val docSettings = new doc.Settings(msg => reporter.error(FakePos("scaladoc"), msg + "\n scaladoc -help gives more information"), + msg => reporter.printMessage(msg)) reporter = new ConsoleReporter(docSettings) { // need to do this so that the Global instance doesn't trash all the // symbols just because there was an error diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 37e3b626e8..3c92c3b4b6 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.diagram.DiagramFactory with model.comment.CommentFactory with model.TreeFactory { override def templateShouldDocument(sym: compiler.Symbol, inTpl: TemplateImpl) = @@ -90,11 +91,12 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor modelFactory.makeModel match { case Some(madeModel) => - if (settings.reportModel) + if (!settings.scaladocQuietRun) println("model contains " + modelFactory.templatesCount + " documentable templates") Some(madeModel) case None => - println("no documentable class found in compilation units") + if (!settings.scaladocQuietRun) + println("no documentable class found in compilation units") None } diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 1cc1f98fba..31e49131f6 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -11,8 +11,9 @@ import java.lang.System import language.postfixOps /** An extended version of compiler settings, with additional Scaladoc-specific options. - * @param error A function that prints a string to the appropriate error stream. */ -class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { + * @param error A function that prints a string to the appropriate error stream + * @param print A function that prints the string, without any extra boilerplate of error */ +class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) extends scala.tools.nsc.Settings(error) { /** A setting that defines in which format the documentation is output. ''Note:'' this setting is currently always * `html`. */ @@ -104,6 +105,12 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { "(for example conversions that require Numeric[String] to be in scope)" ) + val docImplicitsSoundShadowing = BooleanSetting ( + "-implicits-sound-shadowing", + "Use a sound implicit shadowing calculation. Note: this interacts badly with usecases, so " + + "only use it if you haven't defined usecase for implicitly inherited members." + ) + val docDiagrams = BooleanSetting ( "-diagrams", "Create inheritance diagrams for classes, traits and packages." @@ -116,10 +123,44 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { val docDiagramsDotPath = PathSetting ( "-diagrams-dot-path", - "The path to the dot executable used to generate the inheritance diagrams. Ex: /usr/bin/dot", + "The path to the dot executable used to generate the inheritance diagrams. Eg: /usr/bin/dot", "dot" // by default, just pick up the system-wide dot ) + /** The maxium nuber of normal classes to show in the diagram */ + val docDiagramsMaxNormalClasses = IntSetting( + "-diagrams-max-classes", + "The maximum number of superclasses or subclasses to show in a diagram", + 15, + None, + _ => None + ) + + /** The maxium nuber of implcit classes to show in the diagram */ + val docDiagramsMaxImplicitClasses = IntSetting( + "-diagrams-max-implicits", + "The maximum number of implicitly converted classes to show in a diagram", + 10, + None, + _ => None + ) + + val docDiagramsDotTimeout = IntSetting( + "-diagrams-dot-timeout", + "The timeout before the graphviz dot util is forecefully closed, in seconds (default: 10)", + 10, + None, + _ => None + ) + + val docDiagramsDotRestart = IntSetting( + "-diagrams-dot-restart", + "The number of times to restart a malfunctioning dot process before disabling diagrams (default: 5)", + 5, + None, + _ => None + ) + val docRawOutput = BooleanSetting ( "-raw-output", "For each html file, create another .html.raw file containing only the text. (can be used for quickly diffing two scaladoc outputs)" @@ -134,14 +175,16 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { def scaladocSpecific = Set[Settings#Setting]( docformat, doctitle, docfooter, docversion, docUncompilable, docsourceurl, docgenerator, docRootContent, useStupidTypes, docDiagrams, docDiagramsDebug, docDiagramsDotPath, - docImplicits, docImplicitsDebug, docImplicitsShowAll + docDiagramsDotTimeout, docDiagramsDotRestart, + docImplicits, docImplicitsDebug, docImplicitsShowAll, + docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) override def isScaladoc = true - // unset by the testsuite, we don't need to count the entities in the model - var reportModel = true + // set by the testsuite, when checking test output + var scaladocQuietRun = false /** * This is the hardcoded area of Scaladoc. This is where "undesirable" stuff gets eliminated. I know it's not pretty, @@ -188,7 +231,8 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { "scala.Predef.any2stringfmt", "scala.Predef.any2stringadd", "scala.Predef.any2ArrowAssoc", - "scala.Predef.any2Ensuring") + "scala.Predef.any2Ensuring", + "scala.collection.TraversableOnce.alternateImplicit") /** There's a reason all these are specialized by hand but documenting each of them is beyond the point */ val arraySkipConversions = List( diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index 4c9215f923..4a1a8cf898 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -118,11 +118,25 @@ abstract class HtmlPage extends Page { thisPage => case Superscript(in) => { inlineToHtml(in) } case Subscript(in) => { inlineToHtml(in) } case Link(raw, title) => { inlineToHtml(title) } - case EntityLink(entity) => templateToHtml(entity) case Monospace(in) => { inlineToHtml(in) } case Text(text) => xml.Text(text) case Summary(in) => inlineToHtml(in) case HtmlTag(tag) => xml.Unparsed(tag) + case EntityLink(target, template) => template() match { + case Some(tpl) => + templateToHtml(tpl) + case None => + xml.Text(target) + } + } + + def typeToHtml(tpes: List[model.TypeEntity], hasLinks: Boolean): NodeSeq = tpes match { + case Nil => + sys.error("Internal Scaladoc error") + case List(tpe) => + typeToHtml(tpe, hasLinks) + case tpe :: rest => + typeToHtml(tpe, hasLinks) ++ scala.xml.Text(" with ") ++ typeToHtml(rest, hasLinks) } def typeToHtml(tpe: model.TypeEntity, hasLinks: Boolean): NodeSeq = { diff --git a/src/compiler/scala/tools/nsc/doc/html/Page.scala b/src/compiler/scala/tools/nsc/doc/html/Page.scala index 40ae65a37a..5e3ab87ccd 100644 --- a/src/compiler/scala/tools/nsc/doc/html/Page.scala +++ b/src/compiler/scala/tools/nsc/doc/html/Page.scala @@ -97,18 +97,4 @@ abstract class Page { } relativize(thisPage.path.reverse, destPath.reverse).mkString("/") } - - def isExcluded(dtpl: DocTemplateEntity) = { - val qname = dtpl.qualifiedName - ( ( qname.startsWith("scala.Tuple") || qname.startsWith("scala.Product") || - qname.startsWith("scala.Function") || qname.startsWith("scala.runtime.AbstractFunction") - ) && !( - qname == "scala.Tuple1" || qname == "scala.Tuple2" || - qname == "scala.Product" || qname == "scala.Product1" || qname == "scala.Product2" || - qname == "scala.Function" || qname == "scala.Function1" || qname == "scala.Function2" || - qname == "scala.runtime.AbstractFunction0" || qname == "scala.runtime.AbstractFunction1" || - qname == "scala.runtime.AbstractFunction2" - ) - ) - } } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 8ed13e0da2..0e894a03bf 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -61,7 +61,7 @@ class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage { }
      { val tpls: Map[String, Seq[DocTemplateEntity]] = - (pack.templates filter (t => !t.isPackage && !isExcluded(t) )) groupBy (_.name) + (pack.templates filter (t => !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName) )) groupBy (_.name) val placeholderSeq: NodeSeq =
      diff --git a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala index c5fe4da17a..2b68ac2937 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala @@ -62,7 +62,7 @@ class IndexScript(universe: doc.Universe, index: doc.Index) extends Page { def allPackagesWithTemplates = { Map(allPackages.map((key) => { - key -> key.templates.filter(t => !t.isPackage && !isExcluded(t)) + key -> key.templates.filter(t => !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName)) }) : _*) } } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 975ff8fb89..5978489585 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -8,10 +8,11 @@ package doc package html package page -import model._ import scala.xml.{ NodeSeq, Text, UnprefixedAttribute } import language.postfixOps +import model._ + class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { val path = @@ -41,9 +42,12 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage val (absValueMembers, nonAbsValueMembers) = valueMembers partition (_.isAbstract) - val (deprValueMembers, concValueMembers) = + val (deprValueMembers, nonDeprValueMembers) = nonAbsValueMembers partition (_.deprecation.isDefined) + val (concValueMembers, shadowedImplicitMembers) = + nonDeprValueMembers partition (!Template.isShadowedOrAmbiguousImplicit(_)) + val typeMembers = tpl.abstractTypes ++ tpl.aliasTypes ++ tpl.templates.filter(x => x.isTrait || x.isClass) sorted @@ -163,6 +167,13 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
    } + { if (shadowedImplicitMembers.isEmpty) NodeSeq.Empty else +
    +

    Shadowed Implict Value Members

    +
      { shadowedImplicitMembers map (memberToHtml(_, tpl)) }
    +
    + } + { if (deprValueMembers.isEmpty) NodeSeq.Empty else

    Deprecated Value Members

    @@ -244,7 +255,8 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } val memberComment = memberToCommentHtml(mbr, inTpl, false)
  • + data-isabs={ mbr.isAbstract.toString } + fullComment={ if(memberComment.filter(_.label=="div").isEmpty) "no" else "yes" }> { signature(mbr, false) } { memberComment } @@ -299,6 +311,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage

    { inlineToHtml(mbr.comment.get.short) }

    def memberToCommentBodyHtml(mbr: MemberEntity, inTpl: DocTemplateEntity, isSelf: Boolean, isReduced: Boolean = false): NodeSeq = { + val s = universe.settings val memberComment = if (mbr.comment.isEmpty) NodeSeq.Empty @@ -387,6 +400,35 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { targetType } performed by method { conversionMethod } in { conversionOwner }. { constraintText } + } ++ { + if (Template.isShadowedOrAmbiguousImplicit(mbr)) { + // These are the members that are shadowing or ambiguating the current implicit + // see ImplicitMemberShadowing trait for more information + val shadowingSuggestion = { + val params = mbr match { + case d: Def => d.valueParams map (_ map (_ name) mkString("(", ", ", ")")) mkString + case _ => "" // no parameters + } +
    ++ xml.Text("To access this member you can use a ") ++ +
    type ascription ++ xml.Text(":") ++ +
    ++
    {"(" + Template.lowerFirstLetter(tpl.name) + ": " + conv.targetType.name + ")." + mbr.name + params }
    + } + + val shadowingWarning: NodeSeq = + if (Template.isShadowedImplicit(mbr)) + xml.Text("This implicitly inherited member is shadowed by one or more members in this " + + "class.") ++ shadowingSuggestion + else if (Template.isAmbiguousImplicit(mbr)) + xml.Text("This implicitly inherited member is ambiguous. One or more implicitly " + + "inherited members have similar signatures, so calling this member may produce an ambiguous " + + "implicit conversion compiler error.") ++ shadowingSuggestion + else NodeSeq.Empty + +
    Shadowing
    ++ +
    { shadowingWarning }
    + + } else NodeSeq.Empty } case _ => NodeSeq.Empty @@ -562,11 +604,11 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } val subclasses = mbr match { - case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.subClasses.nonEmpty => + case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.allSubClasses.nonEmpty =>
    Known Subclasses
    { - templatesToHtml(dtpl.subClasses.sortBy(_.name), xml.Text(", ")) + templatesToHtml(dtpl.allSubClasses.sortBy(_.name), xml.Text(", ")) }
    case _ => NodeSeq.Empty @@ -605,13 +647,13 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage case PrivateInTemplate(owner) if (owner == mbr.inTemplate) => Some(Paragraph(CText("private"))) case PrivateInTemplate(owner) => - Some(Paragraph(Chain(List(CText("private["), EntityLink(owner), CText("]"))))) + Some(Paragraph(Chain(List(CText("private["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]"))))) case ProtectedInInstance() => Some(Paragraph(CText("protected[this]"))) case ProtectedInTemplate(owner) if (owner == mbr.inTemplate) => Some(Paragraph(CText("protected"))) case ProtectedInTemplate(owner) => - Some(Paragraph(Chain(List(CText("protected["), EntityLink(owner), CText("]"))))) + Some(Paragraph(Chain(List(CText("protected["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]"))))) case Public() => None } @@ -627,7 +669,15 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { - val nameClass = if (mbr.byConversion.isDefined) "implicit" else "name" + val nameClass = + if (Template.isImplicit(mbr)) + if (Template.isShadowedOrAmbiguousImplicit(mbr)) + "implicit shadowed" + else + "implicit" + else + "name" + val nameHtml = { val value = if (mbr.isConstructor) tpl.name else mbr.name val span = if (mbr.deprecation.isDefined) @@ -699,8 +749,8 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } }{ if (isReduced) NodeSeq.Empty else { mbr match { - case tpl: DocTemplateEntity if tpl.parentType.isDefined => - extends { typeToHtml(tpl.parentType.get, hasLinks) } + case tpl: DocTemplateEntity if !tpl.parentTypes.isEmpty => + extends { typeToHtml(tpl.parentTypes.map(_._2), hasLinks) } case tme: MemberEntity if (tme.isDef || tme.isVal || tme.isLazyVal || tme.isVar) => : { typeToHtml(tme.resultType, hasLinks) } @@ -870,5 +920,16 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage xml.Text(ub.typeParamName + " is a subclass of " + ub.upperBound.name + " (" + ub.typeParamName + " <: ") ++ typeToHtml(ub.upperBound, true) ++ xml.Text(")") } +} + +object Template { + + def isImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined + def isShadowedImplicit(mbr: MemberEntity): Boolean = + mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isShadowed).getOrElse(false)).getOrElse(false) + def isAmbiguousImplicit(mbr: MemberEntity): Boolean = + mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isAmbiguous).getOrElse(false)).getOrElse(false) + def isShadowedOrAmbiguousImplicit(mbr: MemberEntity) = isShadowedImplicit(mbr) || isAmbiguousImplicit(mbr) + def lowerFirstLetter(s: String) = if (s.length >= 1) s.substring(0,1).toLowerCase() + s.substring(1) else s } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala new file mode 100644 index 0000000000..16d859894f --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala @@ -0,0 +1,58 @@ +/** + * @author Vlad Ureche + */ +package scala.tools.nsc.doc +package html.page.diagram + +object DiagramStats { + + class TimeTracker(title: String) { + var totalTime: Long = 0l + var maxTime: Long = 0l + var instances: Int = 0 + + def addTime(ms: Long) = { + if (maxTime < ms) + maxTime = ms + totalTime += ms + instances += 1 + } + + def printStats(print: String => Unit) = { + if (instances == 0) + print(title + ": no stats gathered") + else { + print(" " + title) + print(" " + "=" * title.length) + print(" count: " + instances + " items") + print(" total time: " + totalTime + " ms") + print(" average time: " + (totalTime/instances) + " ms") + print(" maximum time: " + maxTime + " ms") + print("") + } + } + } + + private[this] val filterTrack = new TimeTracker("diagrams model filtering") + private[this] val modelTrack = new TimeTracker("diagrams model generation") + private[this] val dotGenTrack = new TimeTracker("dot diagram generation") + private[this] val dotRunTrack = new TimeTracker("dot process runnning") + private[this] val svgTrack = new TimeTracker("svg processing") + + def printStats(settings: Settings) = { + if (settings.docDiagramsDebug.value) { + settings.printMsg("\nDiagram generation running time breakdown:\n") + filterTrack.printStats(settings.printMsg) + modelTrack.printStats(settings.printMsg) + dotGenTrack.printStats(settings.printMsg) + dotRunTrack.printStats(settings.printMsg) + svgTrack.printStats(settings.printMsg) + } + } + + def addFilterTime(ms: Long) = filterTrack.addTime(ms) + def addModelTime(ms: Long) = modelTrack.addTime(ms) + def addDotGenerationTime(ms: Long) = dotGenTrack.addTime(ms) + def addDotRunningTime(ms: Long) = dotRunTrack.addTime(ms) + def addSvgTime(ms: Long) = svgTrack.addTime(ms) +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index 5a1779bba5..b25f0d0b3f 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -333,6 +333,10 @@ div.members > ol > li:last-child { color: darkgreen; } +.signature .symbol .shadowed { + color: darkseagreen; +} + .signature .symbol .params > .implicit { font-style: italic; } @@ -802,4 +806,4 @@ div.fullcomment dl.paramcmts > dd { #mbrsel .showall span { color: #4C4C4C; font-weight: bold; -}*/ \ No newline at end of file +}*/ diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 8c6fa53fbc..e1bbf28dc7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -10,7 +10,7 @@ package model import scala.collection._ import comment._ - +import diagram._ /** An entity in a Scaladoc universe. Entities are declarations in the program and correspond to symbols in the * compiler. Entities model the following Scala concepts: @@ -96,6 +96,9 @@ 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 } @@ -174,6 +177,10 @@ trait MemberEntity extends Entity { /** Whether this member is abstract. */ def isAbstract: Boolean + /** If this symbol is a use case, the useCaseOf will contain the member it was derived from, containing the full + * signature and the complete parameter descriptions. */ + def useCaseOf: Option[MemberEntity] = None + /** If this member originates from an implicit conversion, we set the implicit information to the correct origin */ def byConversion: Option[ImplicitConversion] } @@ -219,8 +226,10 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { * only if the `docsourceurl` setting has been set. */ def sourceUrl: Option[java.net.URL] - /** The direct super-type of this template. */ - def parentType: Option[TypeEntity] + /** The direct super-type of this template + e.g: {{{class A extends B[C[Int]] with D[E]}}} will have two direct parents: class B and D + NOTE: we are dropping the refinement here! */ + def parentTypes: List[(TemplateEntity, TypeEntity)] /** All class, trait and object templates which are part of this template's linearization, in lineratization order. * This template's linearization contains all of its direct and indirect super-classes and super-traits. */ @@ -230,9 +239,13 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { * This template's linearization contains all of its direct and indirect super-types. */ def linearizationTypes: List[TypeEntity] - /**All class, trait and object templates for which this template is a direct or indirect super-class or super-trait. - * Only templates for which documentation is available in the universe (`DocTemplateEntity`) are listed. */ - def subClasses: List[DocTemplateEntity] + /** All class, trait and object templates for which this template is a direct or indirect super-class or super-trait. + * Only templates for which documentation is available in the universe (`DocTemplateEntity`) are listed. */ + def allSubClasses: List[DocTemplateEntity] + + /** All class, trait and object templates for which this template is a *direct* super-class or super-trait. + * Only templates for which documentation is available in the universe (`DocTemplateEntity`) are listed. */ + def directSubClasses: List[DocTemplateEntity] /** All members of this template. If this template is a package, only templates for which documentation is available * in the universe (`DocTemplateEntity`) are listed. */ @@ -260,6 +273,22 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { /** The implicit conversions this template (class or trait, objects and packages are not affected) */ def conversions: List[ImplicitConversion] + + /** The shadowing information for the implicitly added members */ + def implicitsShadowing: Map[MemberEntity, ImplicitMemberShadowing] + + /** Classes that can be implcitly converted to this class */ + def incomingImplicitlyConvertedClasses: List[DocTemplateEntity] + + /** Classes to which this class can be implicitly converted to + NOTE: Some classes might not be included in the scaladoc run so they will be NoDocTemplateEntities */ + def outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity)] + + /** If this template takes place in inheritance and implicit conversion relations, it will be shown in this diagram */ + def inheritanceDiagram: Option[Diagram] + + /** If this template contains other templates, such as classes and traits, they will be shown in this diagram */ + def contentDiagram: Option[Diagram] } @@ -322,10 +351,6 @@ trait NonTemplateMemberEntity extends MemberEntity { * It corresponds to a real member, and provides a simplified, yet compatible signature for that member. */ def isUseCase: Boolean - /** If this symbol is a use case, the useCaseOf will contain the member it was derived from, containing the full - * signature and the complete parameter descriptions. */ - def useCaseOf: Option[MemberEntity] - /** Whether this member is a bridge member. A bridge member does only exist for binary compatibility reasons * and should not appear in ScalaDoc. */ def isBridge: Boolean @@ -444,6 +469,15 @@ trait ImplicitConversion { /** The result type after the conversion */ def targetType: TypeEntity + /** The result type after the conversion + * Note: not all targetTypes have a corresponding template. Examples include conversions resulting in refinement + * types. Need to check it's not option! + */ + def targetTemplate: Option[TemplateEntity] + + /** The components of the implicit conversion type parents */ + def targetTypeComponents: List[(TemplateEntity, TypeEntity)] + /** The entity for the method that performed the conversion, if it's documented (or just its name, otherwise) */ def convertorMethod: Either[MemberEntity, String] @@ -463,12 +497,30 @@ trait ImplicitConversion { def members: List[MemberEntity] } -/** A trait that encapsulates a constraint necessary for implicit conversion */ -trait Constraint { - // /** The implicit conversion during which this constraint appears */ - // def conversion: ImplicitConversion +/** Shadowing captures the information that the member is shadowed by some other members + * There are two cases of implicitly added member shadowing: + * 1) shadowing from a original class member (the class already has that member) + * in this case, it won't be possible to call the member directly, the type checker will fail attempting to adapt + * the call arguments (or if they fit it will call the original class' method) + * 2) shadowing from other possible implicit conversions () + * this will result in an ambiguous implicit converion error + */ +trait ImplicitMemberShadowing { + /** The members that shadow the current entry use .inTemplate to get to the template name */ + def shadowingMembers: List[MemberEntity] + + /** The members that ambiguate this implicit conversion + Note: for ambiguatingMembers you have the following invariant: + assert(ambiguatingMembers.foreach(_.byConversion.isDefined) */ + def ambiguatingMembers: List[MemberEntity] + + def isShadowed: Boolean = !shadowingMembers.isEmpty + def isAmbiguous: Boolean = !ambiguatingMembers.isEmpty } +/** A trait that encapsulates a constraint necessary for implicit conversion */ +trait Constraint + /** A constraint involving a type parameter which must be in scope */ trait ImplicitInScopeConstraint extends Constraint { /** The type of the implicit value required */ diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 9a8df1fd0e..8eb6e358b9 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -6,6 +6,8 @@ package model import comment._ +import diagram._ + import scala.collection._ import scala.util.matching.Regex @@ -17,7 +19,7 @@ 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 CommentFactory with TreeFactory => + thisFactory: ModelFactory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with TreeFactory => import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } @@ -25,7 +27,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def templatesCount = docTemplatesCache.count(_._2.isDocTemplate) - droppedPackages.size - private var modelFinished = false + private var _modelFinished = false + def modelFinished: Boolean = _modelFinished private var universe: Universe = null private def dbg(msg: String) = if (sys.props contains "scala.scaladoc.debug") println(msg) @@ -50,7 +53,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val settings = thisFactory.settings val rootPackage = modelCreation.createRootPackage } - modelFinished = true + _modelFinished = true // complete the links between model entities, everthing that couldn't have been done before universe.rootPackage.completeModel @@ -93,6 +96,7 @@ 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)) def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject } @@ -250,14 +254,26 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } else None } - def parentType = { - if (sym.isPackage || sym == AnyClass) None else { + + def parentTemplates = + if (sym.isPackage || sym == AnyClass) + List() + else + sym.tpe.parents.flatMap { tpe: Type => + val tSym = tpe.typeSymbol + if (tSym != NoSymbol) + List(makeTemplate(tSym)) + else + List() + } filter (_.isInstanceOf[DocTemplateEntity]) + + def parentTypes = + if (sym.isPackage || sym == AnyClass) List() else { val tps = sym.tpe.parents map { _.asSeenFrom(sym.thisType, sym) } - Some(makeType(RefinedType(tps, EmptyScope), inTpl)) + makeParentTypes(RefinedType(tps, EmptyScope), inTpl) } - } - protected def linearizationFromSymbol(symbol: Symbol) = { + protected def linearizationFromSymbol(symbol: Symbol): List[(TemplateEntity, TypeEntity)] = { symbol.ancestors map { ancestor => val typeEntity = makeType(symbol.info.baseType(ancestor), this) val tmplEntity = makeTemplate(ancestor) match { @@ -272,6 +288,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def linearizationTemplates = linearization map { _._1 } def linearizationTypes = linearization map { _._2 } + /* Subclass cache */ private lazy val subClassesCache = ( if (noSubclassCache(sym)) null else mutable.ListBuffer[DocTemplateEntity]() @@ -280,18 +297,40 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { if (subClassesCache != null) subClassesCache += sc } - def subClasses = if (subClassesCache == null) Nil else subClassesCache.toList + def allSubClasses = if (subClassesCache == null) Nil else subClassesCache.toList + def directSubClasses = allSubClasses.filter(_.parentTypes.map(_._1).contains(this)) + + /* Implcitly convertible class cache */ + private var implicitlyConvertibleClassesCache: mutable.ListBuffer[DocTemplateEntity] = null + def registerImplicitlyConvertibleClass(sc: DocTemplateEntity): Unit = { + if (implicitlyConvertibleClassesCache == null) + implicitlyConvertibleClassesCache = mutable.ListBuffer[DocTemplateEntity]() + implicitlyConvertibleClassesCache += sc + } + + def incomingImplicitlyConvertedClasses: List[DocTemplateEntity] = + if (implicitlyConvertibleClassesCache == null) + List() + else + implicitlyConvertibleClassesCache.toList + // the implicit conversions are generated eagerly, but the members generated by implicit conversions are added + // lazily, on completeModel val conversions: List[ImplicitConversionImpl] = if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil + // members as given by the compiler lazy val memberSyms = sym.info.members.filter(s => membersShouldDocument(s, this)) + // the inherited templates (classes, traits or objects) var memberSymsLazy = memberSyms.filter(t => templateShouldDocument(t, this) && !inOriginalOnwer(t, this)) + // the direct members (methods, values, vars, types and directly contained templates) var memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_)) + // the members generated by the symbols in memberSymsEager + val ownMembers = (memberSyms.flatMap(makeMember(_, null, this))) - var members: List[MemberImpl] = (memberSymsEager.flatMap(makeMember(_, null, this))) ::: - (conversions.flatMap((_.members))) // also take in the members from implicit conversions + // all the members that are documentented PLUS the members inherited by implicit conversions + var members: List[MemberImpl] = ownMembers def templates = members collect { case c: DocTemplateEntity => c } def methods = members collect { case d: Def => d } @@ -299,7 +338,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def abstractTypes = members collect { case t: AbstractType => t } def aliasTypes = members collect { case t: AliasType => t } + /** + * This is the final point in the core model creation: no DocTemplates are created after the model has finished, but + * inherited templates and implicit members are added to the members at this point. + */ def completeModel: Unit = { + // DFS completion for (member <- members) member match { case d: DocTemplateImpl => d.completeModel @@ -310,8 +354,30 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // compute linearization to register subclasses linearization + outgoingImplicitlyConvertedClasses + + // the members generated by the symbols in memberSymsEager PLUS the members from the usecases + val allMembers = ownMembers ::: ownMembers.flatMap(_.useCaseOf.map(_.asInstanceOf[MemberImpl])).distinct + implicitsShadowing = makeShadowingTable(allMembers, conversions, this) + // finally, add the members generated by implicit conversions + members :::= conversions.flatMap(_.memberImpls) } + var implicitsShadowing = Map[MemberEntity, ImplicitMemberShadowing]() + + lazy val outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity)] = conversions flatMap (conv => + if (!implicitExcluded(conv.conversionQualifiedName)) + conv.targetTypeComponents map { + case pair@(template, tpe) => + template match { + case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this) + case _ => // nothing + } + pair + } + else List() + ) + override def isTemplate = true lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) def isDocTemplate = true @@ -324,6 +390,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } case _ => None } + + // We make the diagram a lazy val, since we're not sure we'll include the diagrams in the page + lazy val inheritanceDiagram = makeInheritanceDiagram(this) + lazy val contentDiagram = makeContentDiagram(this) } abstract class PackageImpl(sym: Symbol, inTpl: PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package { @@ -383,7 +453,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def typeParams = sym.typeParams map (makeTypeParam(_, inTpl)) } - /* ============== MAKER METHODS ============== */ /** */ @@ -540,10 +609,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { if (bSym == definitions.Object_synchronized) { val cSymInfo = (bSym.info: @unchecked) match { case PolyType(ts, MethodType(List(bp), mt)) => - val cp = bp.cloneSymbol.setInfo(definitions.byNameType(bp.info)) + val cp = bp.cloneSymbol.setPos(bp.pos).setInfo(definitions.byNameType(bp.info)) PolyType(ts, MethodType(List(cp), mt)) } - bSym.cloneSymbol.setInfo(cSymInfo) + bSym.cloneSymbol.setPos(bSym.pos).setInfo(cSymInfo) } else bSym } @@ -726,6 +795,20 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { makeType(tpe, inTpl) } + /** Get the types of the parents of the current class, ignoring the refinements */ + def makeParentTypes(aType: Type, inTpl: => TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { + case RefinedType(parents, defs) => + val ignoreParents = Set[Symbol](AnyClass, ObjectClass) + val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) + filtParents.map(parent => { + val templateEntity = makeTemplate(parent.typeSymbol) + val typeEntity = makeType(parent, inTpl) + (templateEntity, typeEntity) + }) + case _ => + List((makeTemplate(aType.typeSymbol), makeType(aType, inTpl))) + } + /** */ def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { def templatePackage = closestPackage(inTpl.sym) @@ -902,5 +985,11 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { /** Filter '@bridge' methods only if *they don't override non-bridge methods*. See SI-5373 for details */ def isPureBridge(sym: Symbol) = sym.isBridge && sym.allOverriddenSymbols.forall(_.isBridge) + + // the classes that are excluded from the index should also be excluded from the diagrams + def classExcluded(clazz: TemplateEntity): Boolean = settings.hardcoded.isExcluded(clazz.qualifiedName) + + // the implicit conversions that are excluded from the pages should not appear in the diagram + def implicitExcluded(convertorMethod: String): Boolean = settings.hardcoded.commonConversionTargets.contains(convertorMethod) } diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 2a0fcea0ea..e7f4a9c79b 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -64,8 +64,8 @@ trait ModelFactoryImplicitSupport { // debugging: val DEBUG: Boolean = settings.docImplicitsDebug.value val ERROR: Boolean = true // currently we show all errors - @inline final def debug(msg: => String) = if (DEBUG) println(msg) - @inline final def error(msg: => String) = if (ERROR) println(msg) + @inline final def debug(msg: => String) = if (DEBUG) settings.printMsg(msg) + @inline final def error(msg: => String) = if (ERROR) settings.printMsg(msg) /** This is a flag that indicates whether to eliminate implicits that cannot be satisfied within the current scope. * For example, if an implicit conversion requires that there is a Numeric[T] in scope: @@ -80,74 +80,8 @@ trait ModelFactoryImplicitSupport { * - not be generated at all, since there's no Numeric[String] in scope (if ran without -implicits-show-all) * - generated with a *weird* constraint, Numeric[String] as the user might add it by hand (if flag is enabled) */ - val implicitsShowAll: Boolean = settings.docImplicitsShowAll.value class ImplicitNotFound(tpe: Type) extends Exception("No implicit of type " + tpe + " found in scope.") - /* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */ - - class ImplicitConversionImpl( - val sym: Symbol, - val convSym: Symbol, - val toType: Type, - val constrs: List[Constraint], - inTpl: => DocTemplateImpl) - extends ImplicitConversion { - - def source: DocTemplateEntity = inTpl - - def targetType: TypeEntity = makeType(toType, inTpl) - - def convertorOwner: TemplateEntity = makeTemplate(convSym.owner) - - def convertorMethod: Either[MemberEntity, String] = { - var convertor: MemberEntity = null - - convertorOwner match { - case doc: DocTemplateImpl => - val convertors = members.collect { case m: MemberImpl if m.sym == convSym => m } - if (convertors.length == 1) - convertor = convertors.head - case _ => - } - if (convertor ne null) - Left(convertor) - else - Right(convSym.nameString) - } - - def conversionShortName = convSym.nameString - - def conversionQualifiedName = makeQualifiedName(convSym) - - lazy val constraints: List[Constraint] = constrs - - val members: List[MemberImpl] = { - // Obtain the members inherited by the implicit conversion - var memberSyms = toType.members.filter(implicitShouldDocument(_)) - val existingMembers = sym.info.members - - // Debugging part :) - debug(sym.nameString + "\n" + "=" * sym.nameString.length()) - debug(" * conversion " + convSym + " from " + sym.tpe + " to " + toType) - - // Members inherited by implicit conversions cannot override actual members - memberSyms = memberSyms.filterNot((sym1: Symbol) => - existingMembers.exists(sym2 => sym1.name == sym2.name && - !isDistinguishableFrom(toType.memberInfo(sym1), sym.info.memberInfo(sym2)))) - - debug(" -> full type: " + toType) - if (constraints.length != 0) { - debug(" -> constraints: ") - constraints foreach { constr => debug(" - " + constr) } - } - debug(" -> members:") - memberSyms foreach (sym => debug(" - "+ sym.decodedName +" : " + sym.info)) - debug("") - - memberSyms.flatMap((makeMember(_, this, inTpl))) - } - } - /* ============== MAKER METHODS ============== */ /** @@ -166,16 +100,17 @@ trait ModelFactoryImplicitSupport { val results = global.analyzer.allViewsFrom(sym.tpe, context, sym.typeParams) var conversions = results.flatMap(result => makeImplicitConversion(sym, result._1, result._2, context, inTpl)) - conversions = conversions.filterNot(_.members.isEmpty) + // also keep empty conversions, so they appear in diagrams + // conversions = conversions.filter(!_.members.isEmpty) // Filter out specialized conversions from array if (sym == ArrayClass) - conversions = conversions.filterNot((conv: ImplicitConversion) => + conversions = conversions.filterNot((conv: ImplicitConversionImpl) => hardcoded.arraySkipConversions.contains(conv.conversionQualifiedName)) // Filter out non-sensical conversions from value types if (isPrimitiveValueType(sym.tpe)) - conversions = conversions.filter((ic: ImplicitConversion) => + conversions = conversions.filter((ic: ImplicitConversionImpl) => hardcoded.valueClassFilter(sym.nameString, ic.conversionQualifiedName)) // Put the class-specific conversions in front @@ -314,7 +249,7 @@ trait ModelFactoryImplicitSupport { available match { case Some(true) => Nil - case Some(false) if (!implicitsShowAll) => + case Some(false) if (!settings.docImplicitsShowAll.value) => // if -implicits-show-all is not set, we get rid of impossible conversions (such as Numeric[String]) throw new ImplicitNotFound(implType) case _ => @@ -399,6 +334,171 @@ trait ModelFactoryImplicitSupport { sym.ownerChain.filterNot(remove.contains(_)).reverse.map(_.nameString).mkString(".") } + /* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */ + + class ImplicitConversionImpl( + val sym: Symbol, + val convSym: Symbol, + val toType: Type, + val constrs: List[Constraint], + inTpl: => DocTemplateImpl) + extends ImplicitConversion { + + def source: DocTemplateEntity = inTpl + + def targetType: TypeEntity = makeType(toType, inTpl) + + def convertorOwner: TemplateEntity = + if (convSym != NoSymbol) + makeTemplate(convSym.owner) + else { + error("Scaladoc implicits: Implicit conversion from " + sym.tpe + " to " + toType + " done by " + convSym + " = NoSymbol!") + makeRootPackage + } + + def targetTemplate: Option[TemplateEntity] = toType match { + // @Vlad: I'm being extra conservative in template creation -- I don't want to create templates for complex types + // such as refinement types because the template can't represent the type corectly (a template corresponds to a + // package, class, trait or object) + case t: TypeRef => Some(makeTemplate(t.sym)) + case RefinedType(parents, decls) => None + case _ => error("Scaladoc implicits: Could not create template for: " + toType + " of type " + toType.getClass); None + } + + def targetTypeComponents: List[(TemplateEntity, TypeEntity)] = makeParentTypes(toType, inTpl) + + def convertorMethod: Either[MemberEntity, String] = { + var convertor: MemberEntity = null + + convertorOwner match { + case doc: DocTemplateImpl => + val convertors = members.collect { case m: MemberImpl if m.sym == convSym => m } + if (convertors.length == 1) + convertor = convertors.head + case _ => + } + if (convertor ne null) + Left(convertor) + else + Right(convSym.nameString) + } + + def conversionShortName = convSym.nameString + + def conversionQualifiedName = makeQualifiedName(convSym) + + lazy val constraints: List[Constraint] = constrs + + lazy val memberImpls: List[MemberImpl] = { + // Obtain the members inherited by the implicit conversion + val memberSyms = toType.members.filter(implicitShouldDocument(_)) + val existingSyms = sym.info.members + + // Debugging part :) + debug(sym.nameString + "\n" + "=" * sym.nameString.length()) + debug(" * conversion " + convSym + " from " + sym.tpe + " to " + toType) + + debug(" -> full type: " + toType) + if (constraints.length != 0) { + debug(" -> constraints: ") + constraints foreach { constr => debug(" - " + constr) } + } + debug(" -> members:") + memberSyms foreach (sym => debug(" - "+ sym.decodedName +" : " + sym.info)) + debug("") + + memberSyms.flatMap({ aSym => + makeTemplate(aSym.owner) match { + case d: DocTemplateImpl => + // we can't just pick up nodes from the previous template, although that would be very convenient: + // they need the byConversion field to be attached to themselves -- this is design decision I should + // revisit soon + // + // d.ownMembers.collect({ + // // it's either a member or has a couple of usecases it's hidden behind + // case m: MemberImpl if m.sym == aSym => + // m // the member itself + // case m: MemberImpl if m.useCaseOf.isDefined && m.useCaseOf.get.asInstanceOf[MemberImpl].sym == aSym => + // m.useCaseOf.get.asInstanceOf[MemberImpl] // the usecase + // }) + makeMember(aSym, this, d) + case _ => + // should only happen if the code for this template is not part of the scaladoc run => + // members won't have any comments + makeMember(aSym, this, inTpl) + } + }) + } + + lazy val members: List[MemberEntity] = memberImpls + } + + /* ========================= HELPER METHODS ========================== */ + /** + * Computes the shadowing table for all the members in the implicit conversions + * @param mbrs All template's members, including usecases and full signature members + * @param convs All the conversions the template takes part in + * @param inTpl the ususal :) + */ + def makeShadowingTable(mbrs: List[MemberImpl], + convs: List[ImplicitConversionImpl], + inTpl: DocTemplateImpl): Map[MemberEntity, ImplicitMemberShadowing] = { + assert(modelFinished) + + var shadowingTable = Map[MemberEntity, ImplicitMemberShadowing]() + + for (conv <- convs) { + val otherConvs = convs.filterNot(_ == conv) + + for (member <- conv.memberImpls) { + // for each member in our list + val sym1 = member.sym + val tpe1 = conv.toType.memberInfo(sym1) + + // check if it's shadowed by a member in the original class + var shadowedBySyms: List[Symbol] = List() + for (mbr <- mbrs) { + val sym2 = mbr.sym + if (sym1.name == sym2.name) { + val shadowed = !settings.docImplicitsSoundShadowing.value || { + val tpe2 = inTpl.sym.info.memberInfo(sym2) + !isDistinguishableFrom(tpe1, tpe2) + } + if (shadowed) + shadowedBySyms ::= sym2 + } + } + + val shadowedByMembers = mbrs.filter((mb: MemberImpl) => shadowedBySyms.contains(mb.sym)) + + // check if it's shadowed by another member + var ambiguousByMembers: List[MemberEntity] = List() + for (conv <- otherConvs) + for (member2 <- conv.memberImpls) { + val sym2 = member2.sym + if (sym1.name == sym2.name) { + val tpe2 = conv.toType.memberInfo(sym2) + // Ambiguity should be an equivalence relation + val ambiguated = !isDistinguishableFrom(tpe1, tpe2) || !isDistinguishableFrom(tpe2, tpe1) + if (ambiguated) + ambiguousByMembers ::= member2 + } + } + + // we finally have the shadowing info + val shadowing = new ImplicitMemberShadowing { + def shadowingMembers: List[MemberEntity] = shadowedByMembers + def ambiguatingMembers: List[MemberEntity] = ambiguousByMembers + } + + shadowingTable += (member -> shadowing) + } + } + + shadowingTable + } + + /** * uniteConstraints takes a TypeConstraint instance and simplifies the constraints inside * @@ -493,8 +593,8 @@ trait ModelFactoryImplicitSupport { // - common methods (in Any, AnyRef, Object) as they are automatically removed // - private and protected members (not accessible following an implicit conversion) // - members starting with _ (usually reserved for internal stuff) - localShouldDocument(aSym) && (!aSym.isConstructor) && (aSym.owner != ObjectClass) && - (aSym.owner != AnyClass) && (aSym.owner != AnyRefClass) && + localShouldDocument(aSym) && (!aSym.isConstructor) && (aSym.owner != AnyValClass) && + (aSym.owner != AnyClass) && (aSym.owner != ObjectClass) && (!aSym.isProtected) && (!aSym.isPrivate) && (!aSym.name.startsWith("_")) && (aSym.isMethod || aSym.isGetter || aSym.isSetter) && (aSym.nameString != "getClass") @@ -506,15 +606,18 @@ trait ModelFactoryImplicitSupport { * The trick here is that the resultType does not matter - the condition for removal it that paramss have the same * structure (A => B => C may not override (A, B) => C) and that all the types involved are * of the implcit conversion's member are subtypes of the parent members' parameters */ - def isDistinguishableFrom(t1: Type, t2: Type): Boolean = + def isDistinguishableFrom(t1: Type, t2: Type): Boolean = { + // Vlad: I tried using matches but it's not exactly what we need: + // (p: AnyRef)AnyRef matches ((t: String)AnyRef returns false -- but we want that to be true + // !(t1 matches t2) if (t1.paramss.map(_.length) == t2.paramss.map(_.length)) { for ((t1p, t2p) <- t1.paramss.flatten zip t2.paramss.flatten) - if (!isSubType(t1 memberInfo t1p, t2 memberInfo t2p)) - return true // if on the corresponding parameter you give a type that is in t1 but not in t2 - // example: - // def foo(a: Either[Int, Double]): Int = 3 - // def foo(b: Left[T1]): Int = 6 - // a.foo(Right(4.5d)) prints out 3 :) + if (!isSubType(t1 memberInfo t1p, t2 memberInfo t2p)) + return true // if on the corresponding parameter you give a type that is in t1 but not in t2 + // def foo(a: Either[Int, Double]): Int = 3 + // def foo(b: Left[T1]): Int = 6 + // a.foo(Right(4.5d)) prints out 3 :) false } else true // the member structure is different foo(3, 5) vs foo(3)(5) + } } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala index ef4047cebf..ecc3273903 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala @@ -67,8 +67,8 @@ final case class Bold(text: Inline) extends Inline final case class Underline(text: Inline) extends Inline final case class Superscript(text: Inline) extends Inline final case class Subscript(text: Inline) extends Inline +final case class EntityLink(target: String, template: () => Option[TemplateEntity]) extends Inline final case class Link(target: String, title: Inline) extends Inline -final case class EntityLink(target: TemplateEntity) extends Inline final case class Monospace(text: Inline) extends Inline final case class Text(text: String) extends Inline final case class HtmlTag(data: String) extends Inline { diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala index 914275dd8d..7b70683db5 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala @@ -108,6 +108,12 @@ abstract class Comment { /** A description for the primary constructor */ def constructor: Option[Body] + /** A set of diagram directives for the inheritance diagram */ + def inheritDiagram: List[String] + + /** A set of diagram directives for the content diagram */ + def contentDiagram: List[String] + override def toString = body.toString + "\n" + (authors map ("@author " + _.toString)).mkString("\n") + diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala index c749d30267..a46be37d60 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -97,37 +97,41 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => /* Creates comments with necessary arguments */ def createComment ( - body0: Option[Body] = None, - authors0: List[Body] = List.empty, - see0: List[Body] = List.empty, - result0: Option[Body] = None, - throws0: Map[String,Body] = Map.empty, - valueParams0: Map[String,Body] = Map.empty, - typeParams0: Map[String,Body] = Map.empty, - version0: Option[Body] = None, - since0: Option[Body] = None, - todo0: List[Body] = List.empty, - deprecated0: Option[Body] = None, - note0: List[Body] = List.empty, - example0: List[Body] = List.empty, - constructor0: Option[Body] = None, - source0: Option[String] = None + body0: Option[Body] = None, + authors0: List[Body] = List.empty, + see0: List[Body] = List.empty, + result0: Option[Body] = None, + throws0: Map[String,Body] = Map.empty, + valueParams0: Map[String,Body] = Map.empty, + typeParams0: Map[String,Body] = Map.empty, + version0: Option[Body] = None, + since0: Option[Body] = None, + todo0: List[Body] = List.empty, + deprecated0: Option[Body] = None, + note0: List[Body] = List.empty, + example0: List[Body] = List.empty, + constructor0: Option[Body] = None, + source0: Option[String] = None, + inheritDiagram0: List[String] = List.empty, + contentDiagram0: List[String] = List.empty ) : Comment = new Comment{ - val body = if(body0 isDefined) body0.get else Body(Seq.empty) - val authors = authors0 - val see = see0 - val result = result0 - val throws = throws0 - val valueParams = valueParams0 - val typeParams = typeParams0 - val version = version0 - val since = since0 - val todo = todo0 - val deprecated = deprecated0 - val note = note0 - val example = example0 - val constructor = constructor0 - val source = source0 + val body = if(body0 isDefined) body0.get else Body(Seq.empty) + val authors = authors0 + val see = see0 + val result = result0 + val throws = throws0 + val valueParams = valueParams0 + val typeParams = typeParams0 + val version = version0 + val since = since0 + val todo = todo0 + val deprecated = deprecated0 + val note = note0 + val example = example0 + val constructor = constructor0 + val source = source0 + val inheritDiagram = inheritDiagram0 + val contentDiagram = contentDiagram0 } protected val endOfText = '\u0003' @@ -186,6 +190,10 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => protected val safeTagMarker = '\u000E' + /** A Scaladoc tag not linked to a symbol and not followed by text */ + protected val SingleTag = + new Regex("""\s*@(\S+)\s*""") + /** A Scaladoc tag not linked to a symbol. Returns the name of the tag, and the rest of the line. */ protected val SimpleTag = new Regex("""\s*@(\S+)\s+(.*)""") @@ -306,6 +314,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => val value = body :: tags.getOrElse(key, Nil) parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock) + case SingleTag(name) :: ls if (!inCodeBlock) => + val key = SimpleTagKey(name) + val value = "" :: tags.getOrElse(key, Nil) + parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock) + case line :: ls if (lastTagKey.isDefined) => val key = lastTagKey.get val value = @@ -321,9 +334,24 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => parse0(docBody, tags, lastTagKey, ls, inCodeBlock) case Nil => + // Take the {inheritance, content} diagram keys aside, as it doesn't need any parsing + val inheritDiagramTag = SimpleTagKey("inheritanceDiagram") + val contentDiagramTag = SimpleTagKey("contentDiagram") + + val inheritDiagramText: List[String] = tags.get(inheritDiagramTag) match { + case Some(list) => list + case None => List.empty + } + + val contentDiagramText: List[String] = tags.get(contentDiagramTag) match { + case Some(list) => list + case None => List.empty + } + + val tagsWithoutDiagram = tags.filterNot(pair => pair._1 == inheritDiagramTag || pair._1 == contentDiagramTag) val bodyTags: mutable.Map[TagKey, List[Body]] = - mutable.Map(tags mapValues {tag => tag map (parseWiki(_, pos))} toSeq: _*) + mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos))} toSeq: _*) def oneTag(key: SimpleTagKey): Option[Body] = ((bodyTags remove key): @unchecked) match { @@ -356,21 +384,23 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => } val com = createComment ( - body0 = Some(parseWiki(docBody.toString, pos)), - authors0 = allTags(SimpleTagKey("author")), - see0 = allTags(SimpleTagKey("see")), - result0 = oneTag(SimpleTagKey("return")), - throws0 = allSymsOneTag(SimpleTagKey("throws")), - valueParams0 = allSymsOneTag(SimpleTagKey("param")), - typeParams0 = allSymsOneTag(SimpleTagKey("tparam")), - version0 = oneTag(SimpleTagKey("version")), - since0 = oneTag(SimpleTagKey("since")), - todo0 = allTags(SimpleTagKey("todo")), - deprecated0 = oneTag(SimpleTagKey("deprecated")), - note0 = allTags(SimpleTagKey("note")), - example0 = allTags(SimpleTagKey("example")), - constructor0 = oneTag(SimpleTagKey("constructor")), - source0 = Some(clean(src).mkString("\n")) + body0 = Some(parseWiki(docBody.toString, pos)), + authors0 = allTags(SimpleTagKey("author")), + see0 = allTags(SimpleTagKey("see")), + result0 = oneTag(SimpleTagKey("return")), + throws0 = allSymsOneTag(SimpleTagKey("throws")), + valueParams0 = allSymsOneTag(SimpleTagKey("param")), + typeParams0 = allSymsOneTag(SimpleTagKey("tparam")), + version0 = oneTag(SimpleTagKey("version")), + since0 = oneTag(SimpleTagKey("since")), + todo0 = allTags(SimpleTagKey("todo")), + deprecated0 = oneTag(SimpleTagKey("deprecated")), + note0 = allTags(SimpleTagKey("note")), + example0 = allTags(SimpleTagKey("example")), + constructor0 = oneTag(SimpleTagKey("constructor")), + source0 = Some(clean(src).mkString("\n")), + inheritDiagram0 = inheritDiagramText, + contentDiagram0 = contentDiagramText ) for ((key, _) <- bodyTags) @@ -686,13 +716,6 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => ) } - def entityLink(query: String): Inline = findTemplate(query) match { - case Some(tpl) => - EntityLink(tpl) - case None => - Text(query) - } - def link(): Inline = { val SchemeUri = """([^:]+:.*)""".r jump("[[") @@ -717,7 +740,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => if (!qualName.contains(".") && !definitions.packageExists(qualName)) reportError(pos, "entity link to " + qualName + " should be a fully qualified name") - entityLink(qualName) + // move the template resolution as late as possible + EntityLink(qualName, () => findTemplate(qualName)) } } diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala new file mode 100644 index 0000000000..28a8c7d37d --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -0,0 +1,143 @@ +package scala.tools.nsc.doc +package model +package diagram + +import model._ + +/** + * The diagram base classes + * + * @author Damien Obrist + * @author Vlad Ureche + */ +abstract class Diagram { + def nodes: List[Node] + def edges: List[(Node, List[Node])] + def isPackageDiagram = false + def isClassDiagram = false + def depthInfo: DepthInfo +} + +case class PackageDiagram(nodes:List[/*Class*/Node], edges:List[(Node, List[Node])]) extends Diagram { + override def isPackageDiagram = true + lazy val depthInfo = new PackageDiagramDepth(this) +} + +/** A class diagram */ +case class ClassDiagram(thisNode: ThisNode, + superClasses: List[/*Class*/Node], + subClasses: List[/*Class*/Node], + incomingImplicits: List[ImplicitNode], + outgoingImplicits: List[ImplicitNode]) extends Diagram { + def nodes = thisNode :: superClasses ::: subClasses ::: incomingImplicits ::: outgoingImplicits + def edges = (thisNode -> (superClasses ::: outgoingImplicits)) :: + (subClasses ::: incomingImplicits).map(_ -> List(thisNode)) + + override def isClassDiagram = true + lazy val depthInfo = new DepthInfo { + def maxDepth = 3 + def nodeDepth(node: Node) = + if (node == thisNode) 1 + else if (superClasses.contains(node)) 0 + else if (subClasses.contains(node)) 2 + else if (incomingImplicits.contains(node) || outgoingImplicits.contains(node)) 1 + else -1 + } +} + +trait DepthInfo { + /** Gives the maximum depth */ + def maxDepth: Int + /** Gives the depth of any node in the diagram or -1 if the node is not in the diagram */ + def nodeDepth(node: Node): Int +} + +abstract class Node { + def name = tpe.name + def tpe: TypeEntity + def tpl: Option[TemplateEntity] + /** shortcut to get a DocTemplateEntity */ + def doctpl: Option[DocTemplateEntity] = tpl match { + case Some(tpl) => tpl match { + case d: DocTemplateEntity => Some(d) + case _ => None + } + case _ => None + } + /* shortcuts to find the node type without matching */ + def isThisNode = false + def isNormalNode = false + def isClassNode = if (tpl.isDefined) (tpl.get.isClass || tpl.get.qualifiedName == "scala.AnyRef") else false + def isTraitNode = if (tpl.isDefined) tpl.get.isTrait else false + def isObjectNode= if (tpl.isDefined) tpl.get.isObject else false + def isOtherNode = !(isClassNode || isTraitNode || isObjectNode) + def isImplicitNode = false + def isOutsideNode = false +} + +// different matchers, allowing you to use the pattern matcher against any node +// NOTE: A ThisNode or ImplicitNode can at the same time be ClassNode/TraitNode/OtherNode, not exactly according to +// case class specification -- thus a complete match would be: +// node match { +// case ThisNode(tpe, _) => /* case for this node, you can still use .isClass, .isTrait and .isOther */ +// case ImplicitNode(tpe, _) => /* case for an implicit node, you can still use .isClass, .isTrait and .isOther */ +// case _ => node match { +// case ClassNode(tpe, _) => /* case for a non-this, non-implicit Class node */ +// case TraitNode(tpe, _) => /* case for a non-this, non-implicit Trait node */ +// case OtherNode(tpe, _) => /* case for a non-this, non-implicit Other node */ +// } +// } +object Node { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = Some((n.tpe, n.tpl)) } +object ClassNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isClassNode) Some((n.tpe, n.tpl)) else None } +object TraitNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTraitNode) Some((n.tpe, n.tpl)) else None } +object ObjectNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isObjectNode) Some((n.tpe, n.tpl)) else None } +object OutsideNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOutsideNode) Some((n.tpe, n.tpl)) else None } +object OtherNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOtherNode) Some((n.tpe, n.tpl)) else None } + + + +/** The node for the current class */ +case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isThisNode = true } + +/** The usual node */ +case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) 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]) 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 package (and @contentDiagram showInheritedNodes annotation) */ +case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isOutsideNode = true } + + +// Computing and offering node depth information +class PackageDiagramDepth(pack: PackageDiagram) extends DepthInfo { + private[this] var _maxDepth = 0 + private[this] var _nodeDepth = Map[Node, Int]() + private[this] var seedNodes = Set[Node]() + private[this] val invertedEdges: Map[Node, List[Node]] = + pack.edges.flatMap({case (node: Node, outgoing: List[Node]) => outgoing.map((_, node))}).groupBy(_._1).map({case (k, values) => (k, values.map(_._2))}).withDefaultValue(Nil) + private[this] val directEdges: Map[Node, List[Node]] = pack.edges.toMap.withDefaultValue(Nil) + + // seed base nodes, to minimize noise - they can't all have parents, else there would only be cycles + seedNodes ++= pack.nodes.filter(directEdges(_).isEmpty) + + while (!seedNodes.isEmpty) { + var newSeedNodes = Set[Node]() + for (node <- seedNodes) { + val depth = 1 + (-1 :: directEdges(node).map(_nodeDepth.getOrElse(_, -1))).max + if (depth != _nodeDepth.getOrElse(node, -1)) { + _nodeDepth += (node -> depth) + newSeedNodes ++= invertedEdges(node) + if (depth > _maxDepth) _maxDepth = depth + } + } + seedNodes = newSeedNodes + } + + val maxDepth = _maxDepth + def nodeDepth(node: Node) = _nodeDepth.getOrElse(node, -1) +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala new file mode 100644 index 0000000000..beaa045df4 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala @@ -0,0 +1,248 @@ +package scala.tools.nsc.doc +package model +package diagram + +import model._ +import comment.CommentFactory +import java.util.regex.{Pattern, Matcher} +import scala.util.matching.Regex + +// statistics +import html.page.diagram.DiagramStats + +/** + * This trait takes care of parsing @{inheritance, content}Diagram annotations + * + * @author Damien Obrist + * @author Vlad Ureche + */ +trait DiagramDirectiveParser { + this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory => + + ///// DIAGRAM FILTERS ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * The DiagramFilter trait directs the diagram engine about the way the diagram should be displayed + * + * Vlad: There's an explanation I owe to people using diagrams and not finding a way to hide a specific class from + * all diagrams at once. So why did I choose to allow you to only control the diagrams at class level? So, the + * reason is you would break the separate scaladoc compilation: + * If you have an "@diagram hideMyClass" annotation in class A and you run scaladoc on it along with its subclass B + * A will not appear in B's diagram. But if you scaladoc only on B, A's comment will not be parsed and the + * instructions to hide class A from all diagrams will not be available. Thus I prefer to force you to control the + * diagrams of each class locally. The problem does not appear with scalac, as scalac stores all its necessary + * information (like scala signatures) serialized in the .class file. But we couldn't store doc comments in the class + * file, could we? (Turns out we could, but that's another story) + * + * Any flaming for this decision should go to scala-internals@googlegroups.com + */ + trait DiagramFilter { + /** A flag to hide the diagram completely */ + def hideDiagram: Boolean + /** Hide incoming implicit conversions (for type hierarchy diagrams) */ + def hideIncomingImplicits: Boolean + /** Hide outgoing implicit conversions (for type hierarchy diagrams) */ + def hideOutgoingImplicits: Boolean + /** Hide superclasses (for type hierarchy diagrams) */ + def hideSuperclasses: Boolean + /** Hide subclasses (for type hierarchy diagrams) */ + def hideSubclasses: Boolean + /** Show related classes from other objects/traits/packages (for content diagrams) */ + def showInheritedNodes: Boolean + /** Hide a node from the diagram */ + def hideNode(clazz: TemplateEntity): Boolean + /** Hide an edge from the diagram */ + def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean + } + + /** Main entry point into this trait: generate the filter for inheritance diagrams */ + def makeInheritanceDiagramFilter(template: DocTemplateImpl): DiagramFilter = { + val defaultFilter = if (template.isClass || template.isTrait) FullDiagram else NoDiagramAtAll + if (template.comment.isDefined) + makeDiagramFilter(template, template.comment.get.inheritDiagram, defaultFilter, true) + else + defaultFilter + } + + /** Main entry point into this trait: generate the filter for content diagrams */ + def makeContentDiagramFilter(template: DocTemplateImpl): DiagramFilter = { + val defaultFilter = if (template.isPackage || template.isObject) FullDiagram else NoDiagramAtAll + if (template.comment.isDefined) + makeDiagramFilter(template, template.comment.get.contentDiagram, defaultFilter, false) + else + defaultFilter + } + + protected var tFilter = 0l + protected var tModel = 0l + + /** Show the entire diagram, no filtering */ + case object FullDiagram extends DiagramFilter { + val hideDiagram: Boolean = false + val hideIncomingImplicits: Boolean = false + val hideOutgoingImplicits: Boolean = false + val hideSuperclasses: Boolean = false + val hideSubclasses: Boolean = false + val showInheritedNodes: Boolean = false + def hideNode(clazz: TemplateEntity): Boolean = false + def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = false + } + + /** Hide the diagram completely, no need for special filtering */ + case object NoDiagramAtAll extends DiagramFilter { + val hideDiagram: Boolean = true + val hideIncomingImplicits: Boolean = true + val hideOutgoingImplicits: Boolean = true + val hideSuperclasses: Boolean = true + val hideSubclasses: Boolean = true + val showInheritedNodes: Boolean = false + def hideNode(clazz: TemplateEntity): Boolean = true + def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = true + } + + /** The AnnotationDiagramFilter trait directs the diagram engine according to an annotation + * TODO: Should document the annotation, for now see parseDiagramAnnotation in ModelFactory.scala */ + case class AnnotationDiagramFilter(hideDiagram: Boolean, + hideIncomingImplicits: Boolean, + hideOutgoingImplicits: Boolean, + hideSuperclasses: Boolean, + hideSubclasses: Boolean, + showInheritedNodes: Boolean, + hideNodesFilter: List[Pattern], + hideEdgesFilter: List[(Pattern, Pattern)]) extends DiagramFilter { + + def hideNode(clazz: TemplateEntity): Boolean = { + val qualifiedName = clazz.qualifiedName + for (hideFilter <- hideNodesFilter) + if (hideFilter.matcher(qualifiedName).matches) { + // println(hideFilter + ".matcher(" + qualifiedName + ").matches = " + hideFilter.matcher(qualifiedName).matches) + return true + } + false + } + + def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = { + val clazz1Name = clazz1.qualifiedName + val clazz2Name = clazz2.qualifiedName + for ((clazz1Filter, clazz2Filter) <- hideEdgesFilter) { + if (clazz1Filter.matcher(clazz1Name).matches && + clazz2Filter.matcher(clazz2Name).matches) { + // println(clazz1Filter + ".matcher(" + clazz1Name + ").matches = " + clazz1Filter.matcher(clazz1Name).matches) + // println(clazz2Filter + ".matcher(" + clazz2Name + ").matches = " + clazz2Filter.matcher(clazz2Name).matches) + return true + } + } + false + } + } + + // TODO: This could certainly be improved -- right now the only regex is *, but there's no way to match a single identifier + private val NodeSpecRegex = "\\\"[A-Za-z\\*][A-Za-z\\.\\*]*\\\"" + private val NodeSpecPattern = Pattern.compile(NodeSpecRegex) + private val EdgeSpecRegex = "\\(" + NodeSpecRegex + "\\s*\\->\\s*" + NodeSpecRegex + "\\)" + private val EdgeSpecPattern = Pattern.compile(NodeSpecRegex) + // And the composed regexes: + private val HideNodesRegex = new Regex("^hideNodes(\\s*" + NodeSpecRegex + ")+$") + private val HideEdgesRegex = new Regex("^hideEdges(\\s*" + EdgeSpecRegex + ")+$") + + private def makeDiagramFilter(template: DocTemplateImpl, + directives: List[String], + defaultFilter: DiagramFilter, + isInheritanceDiagram: Boolean): DiagramFilter = directives match { + + // if there are no specific diagram directives, return the default filter (either FullDiagram or NoDiagramAtAll) + case Nil => + defaultFilter + + // compute the exact filters. By including the annotation, the diagram is autmatically added + case _ => + tFilter -= System.currentTimeMillis + var hideDiagram0: Boolean = false + var hideIncomingImplicits0: Boolean = false + var hideOutgoingImplicits0: Boolean = false + var hideSuperclasses0: Boolean = false + var hideSubclasses0: Boolean = false + var showInheritedNodes0: Boolean = false + var hideNodesFilter0: List[Pattern] = Nil + var hideEdgesFilter0: List[(Pattern, Pattern)] = Nil + + def warning(message: String) = { + // we need the position from the package object (well, ideally its comment, but yeah ...) + val sym = if (template.sym.isPackage) template.sym.info.member(global.nme.PACKAGE) else template.sym + assert((sym != global.NoSymbol) || (sym == global.definitions.RootPackage)) + global.reporter.warning(sym.pos, message) + } + + def preparePattern(className: String) = + "^" + className.stripPrefix("\"").stripSuffix("\"").replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*") + "$" + + // separate entries: + val entries = directives.foldRight("")(_ + " " + _).split(",").map(_.trim) + for (entry <- entries) + entry match { + case "hideDiagram" => + hideDiagram0 = true + case "hideIncomingImplicits" if isInheritanceDiagram => + hideIncomingImplicits0 = true + case "hideOutgoingImplicits" if isInheritanceDiagram => + hideOutgoingImplicits0 = true + case "hideSuperclasses" if isInheritanceDiagram => + hideSuperclasses0 = true + case "hideSubclasses" if isInheritanceDiagram => + hideSubclasses0 = true + case "showInheritedNodes" if !isInheritanceDiagram => + showInheritedNodes0 = true + case HideNodesRegex(last) => + val matcher = NodeSpecPattern.matcher(entry) + while (matcher.find()) { + val classPattern = Pattern.compile(preparePattern(matcher.group())) + hideNodesFilter0 ::= classPattern + } + case HideEdgesRegex(last) => + val matcher = NodeSpecPattern.matcher(entry) + while (matcher.find()) { + val class1Pattern = Pattern.compile(preparePattern(matcher.group())) + assert(matcher.find()) // it's got to be there, just matched it! + val class2Pattern = Pattern.compile(preparePattern(matcher.group())) + hideEdgesFilter0 ::= ((class1Pattern, class2Pattern)) + } + case "" => + // don't need to do anything about it + case _ => + warning("Could not understand diagram annotation in " + template.kind + " " + template.qualifiedName + + ": unmatched entry \"" + entry + "\".\n" + + " This could be because:\n" + + " - you forgot to separate entries by commas\n" + + " - you used a tag that is not allowed in the current context (like @contentDiagram hideSuperclasses)\n"+ + " - you did not use one of the allowed tags (see docs.scala-lang.org for scaladoc annotations)") + } + val result = + if (hideDiagram0) + NoDiagramAtAll + else if ((hideNodesFilter0.isEmpty) && + (hideEdgesFilter0.isEmpty) && + (hideIncomingImplicits0 == false) && + (hideOutgoingImplicits0 == false) && + (hideSuperclasses0 == false) && + (hideSubclasses0 == false) && + (showInheritedNodes0 == false) && + (hideDiagram0 == false)) + FullDiagram + else + AnnotationDiagramFilter( + hideDiagram = hideDiagram0, + hideIncomingImplicits = hideIncomingImplicits0, + hideOutgoingImplicits = hideOutgoingImplicits0, + hideSuperclasses = hideSuperclasses0, + hideSubclasses = hideSubclasses0, + showInheritedNodes = showInheritedNodes0, + hideNodesFilter = hideNodesFilter0, + hideEdgesFilter = hideEdgesFilter0) + + if (settings.docDiagramsDebug.value && result != NoDiagramAtAll && result != FullDiagram) + settings.printMsg(template.kind + " " + template.qualifiedName + " filter: " + result) + tFilter += System.currentTimeMillis + + result + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala new file mode 100644 index 0000000000..4ae5e7a5cb --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -0,0 +1,197 @@ +package scala.tools.nsc.doc +package model +package diagram + +import model._ +import comment.CommentFactory +import collection.mutable + +// statistics +import html.page.diagram.DiagramStats + +/** + * This trait takes care of generating the diagram for classes and packages + * + * @author Damien Obrist + * @author Vlad Ureche + */ +trait DiagramFactory extends DiagramDirectiveParser { + this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory => + + /** Create the inheritance diagram for this template */ + def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { + + tFilter = 0 + tModel = -System.currentTimeMillis + + // the diagram filter + val diagramFilter = makeInheritanceDiagramFilter(tpl) + + val result = + if (diagramFilter == NoDiagramAtAll) + None + else { + // the main node + val thisNode = ThisNode(tpl.ownType, Some(tpl)) + + // superclasses + var superclasses = List[Node]() + tpl.parentTypes.collect { case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => p } foreach { + t: (TemplateEntity, TypeEntity) => + val n = NormalNode(t._2, Some(t._1)) + superclasses ::= n + } + val filteredSuperclasses = if (diagramFilter.hideSuperclasses) Nil else superclasses + + // incoming implcit conversions + lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map(tpl => ImplicitNode(tpl.ownType, Some(tpl))) + val filteredIncomingImplicits = if (diagramFilter.hideIncomingImplicits) Nil else incomingImplicitNodes + + // subclasses + val subclasses = tpl.directSubClasses.flatMap { + case d: TemplateEntity if !classExcluded(d) => List(NormalNode(d.ownType, Some(d))) + case _ => Nil + } + val filteredSubclasses = if (diagramFilter.hideSubclasses) Nil else subclasses + + // outgoing implicit coversions + lazy val implicitNodes = tpl.outgoingImplicitlyConvertedClasses.map(pair => ImplicitNode(pair._2, Some(pair._1))) + val filteredImplicitOutgoingNodes = if (diagramFilter.hideOutgoingImplicits) Nil else implicitNodes + + // final diagram filter + filterDiagram(ClassDiagram(thisNode, filteredSuperclasses.reverse, filteredSubclasses.reverse, filteredIncomingImplicits, filteredImplicitOutgoingNodes), diagramFilter) + } + + tModel += System.currentTimeMillis + DiagramStats.addFilterTime(tFilter) + DiagramStats.addModelTime(tModel-tFilter) + + result + } + + /** Create the content diagram for this template */ + def makeContentDiagram(pack: DocTemplateImpl): Option[Diagram] = { + + tFilter = 0 + tModel = -System.currentTimeMillis + + // the diagram filter + val diagramFilter = makeContentDiagramFilter(pack) + + val result = + if (diagramFilter == NoDiagramAtAll) + None + else { + var mapNodes = Map[DocTemplateEntity, Node]() + var nodesShown = Set[DocTemplateEntity]() + var edgesAll = List[(DocTemplateEntity, List[DocTemplateEntity])]() + + // classes is the entire set of classes and traits in the package, they are the superset of nodes in the diagram + // we collect classes, traits and objects without a companion, which are usually used as values(e.g. scala.None) + val dnodes = pack.members collect { + case d: DocTemplateEntity if d.isClass || d.isTrait || (d.isObject && !d.companion.isDefined) && + ((d.inTemplate == pack) || diagramFilter.showInheritedNodes) => d + } + + // for each node, add its subclasses + for (node <- dnodes if !classExcluded(node)) { + val superClasses = node.parentTypes.collect { + case (tpl: DocTemplateEntity, tpe) if tpl.inTemplate == pack && !classExcluded(tpl) => tpl + case (tpl: DocTemplateEntity, tpe) if tpl.inTemplate != pack && !classExcluded(tpl) && diagramFilter.showInheritedNodes && (pack.members contains tpl) => tpl + } + + if (!superClasses.isEmpty) { + nodesShown += node + nodesShown ++= superClasses + } + + edgesAll ::= node -> superClasses + mapNodes += node -> (if (node.inTemplate == pack) NormalNode(node.ownType, Some(node)) else OutsideNode(node.ownType, Some(node))) + } + + if (nodesShown.isEmpty) + None + else { + val nodes = dnodes.filter(nodesShown.contains(_)).map(mapNodes(_)) + val edges = edgesAll.map(pair => (mapNodes(pair._1), pair._2.map(mapNodes(_)))).filterNot(pair => pair._2.isEmpty) + filterDiagram(PackageDiagram(nodes, edges), diagramFilter) + } + } + + tModel += System.currentTimeMillis + DiagramStats.addFilterTime(tFilter) + DiagramStats.addModelTime(tModel-tFilter) + + result + } + + /** Diagram filtering logic */ + private def filterDiagram(diagram: Diagram, diagramFilter: DiagramFilter): Option[Diagram] = { + tFilter -= System.currentTimeMillis + + val result = + if (diagramFilter == FullDiagram) + Some(diagram) + else if (diagramFilter == NoDiagramAtAll) + None + else { + // Final diagram, with the filtered nodes and edges + diagram match { + case ClassDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode.tpl.get) => + None + + case ClassDiagram(thisNode, superClasses, subClasses, incomingImplicits, outgoingImplicits) => + + def hideIncoming(node: Node): Boolean = + if (node.tpl.isDefined) diagramFilter.hideNode(node.tpl.get) || diagramFilter.hideEdge(node.tpl.get, thisNode.tpl.get) + else false // hopefully we won't need to fallback here + + def hideOutgoing(node: Node): Boolean = + if (node.tpl.isDefined) diagramFilter.hideNode(node.tpl.get) || diagramFilter.hideEdge(thisNode.tpl.get, node.tpl.get) + else false // hopefully we won't need to fallback here + + // println(thisNode) + // println(superClasses.map(cl => "super: " + cl + " " + hideOutgoing(cl)).mkString("\n")) + // println(subClasses.map(cl => "sub: " + cl + " " + hideIncoming(cl)).mkString("\n")) + Some(ClassDiagram(thisNode, + superClasses.filterNot(hideOutgoing(_)), + subClasses.filterNot(hideIncoming(_)), + incomingImplicits.filterNot(hideIncoming(_)), + outgoingImplicits.filterNot(hideOutgoing(_)))) + + case PackageDiagram(nodes0, edges0) => + // Filter out all edges that: + // (1) are sources of hidden classes + // (2) are manually hidden by the user + // (3) are destinations of hidden classes + val edges: List[(Node, List[Node])] = + diagram.edges.flatMap({ + case (source@Node(_, Some(tpl1)), dests) if !diagramFilter.hideNode(tpl1) => + val dests2 = dests.collect({ case node@Node(_, Some(tpl2)) if (!(diagramFilter.hideEdge(tpl1, tpl2) || diagramFilter.hideNode(tpl2))) => node }) + if (dests2 != Nil) + List((source, dests2)) + else + Nil + case _ => Nil + }) + + // Only show the the non-isolated nodes + // TODO: Decide if we really want to hide package members, I'm not sure that's a good idea (!!!) + // TODO: Does .distinct cause any stability issues? + val sourceNodes = edges.map(_._1) + val sinkNodes = edges.map(_._2).flatten + val nodes = (sourceNodes ::: sinkNodes).distinct + Some(PackageDiagram(nodes, edges)) + } + } + + tFilter += System.currentTimeMillis + + // eliminate all empty diagrams + if (result.isDefined && result.get.edges.forall(_._2.isEmpty)) + None + else + result + } + +} diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index 142f2baea5..de5354d4a0 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -81,9 +81,9 @@ abstract class ScaladocModelTest extends DirectTest { private[this] var settings: Settings = null // create a new scaladoc compiler - def newDocFactory: DocFactory = { + private[this] def newDocFactory: DocFactory = { settings = new Settings(_ => ()) - settings.reportModel = false // yaay, no more "model contains X documentable templates"! + settings.scaladocQuietRun = true // yaay, no more "model contains X documentable templates"! val args = extraSettings + " " + scaladocSettings val command = new ScalaDoc.Command((CommandLineParser tokenize (args)), settings) val docFact = new DocFactory(new ConsoleReporter(settings), settings) diff --git a/test/scaladoc/resources/implicits-ambiguating-res.scala b/test/scaladoc/resources/implicits-ambiguating-res.scala new file mode 100644 index 0000000000..6ed51366cb --- /dev/null +++ b/test/scaladoc/resources/implicits-ambiguating-res.scala @@ -0,0 +1,72 @@ +/** + * Test scaladoc implicits distinguishing -- supress all members by implicit conversion that are shadowed by the + * class' own members + * + * {{{ + * scala> class A { def foo(t: String) = 4 } + * defined class A + * + * scala> class B { def foo(t: Any) = 5 } + * defined class B + * + * scala> implicit def AtoB(a:A) = new B + * AtoB: (a: A)B + * + * scala> val a = new A + * a: A = A@28f553e3 + * + * scala> a.foo("T") + * res1: Int = 4 + * + * scala> a.foo(4) + * res2: Int = 5 + * }}} + */ +package scala.test.scaladoc.implicits.ambiguating +import language.implicitConversions // according to SIP18 + +/** - conv1-5 should be ambiguous + * - conv6-7 should not be ambiguous + * - conv8 should be ambiguous + * - conv9 should be ambiguous + * - conv10 and conv11 should not be ambiguous */ +class A[T] +/** conv1-9 should be the same, conv10 should be ambiguous, conv11 should be okay */ +class B extends A[Int] +/** conv1-9 should be the same, conv10 and conv11 should not be ambiguous */ +class C extends A[Double] + /** conv1-9 should be the same, conv10 should not be ambiguous while conv11 should be ambiguous */ +class D extends A[AnyRef] + +class X[T] { + def conv1: AnyRef = ??? + def conv2: T = ??? + def conv3(l: Int): AnyRef = ??? + def conv4(l: AnyRef): AnyRef = ??? + def conv5(l: AnyRef): String = ??? + def conv6(l: String)(m: String): AnyRef = ??? + def conv7(l: AnyRef)(m: AnyRef): AnyRef = ??? + def conv8(l: AnyRef): AnyRef = ??? + def conv9(l: String): AnyRef = ??? + def conv10(l: T): T = ??? + def conv11(l: T): T = ??? +} + +class Z[T] { + def conv1: AnyRef = ??? + def conv2: T = ??? + def conv3(p: Int): AnyRef = ??? + def conv4(p: AnyRef): String = ??? + def conv5(p: AnyRef): AnyRef = ??? + def conv6(p: String, q: String): AnyRef = ??? + def conv7(p: AnyRef, q: AnyRef): AnyRef = ??? + def conv8(p: String): AnyRef = ??? + def conv9(p: AnyRef): AnyRef = ??? + def conv10(p: Int): T = ??? + def conv11(p: String): T = ??? +} + +object A { + implicit def AtoX[T](a: A[T]) = new X[T] + implicit def AtoZ[T](a: A[T]) = new Z[T] +} diff --git a/test/scaladoc/resources/implicits-base-res.scala b/test/scaladoc/resources/implicits-base-res.scala index 65d7bdf67c..d6c0332c10 100644 --- a/test/scaladoc/resources/implicits-base-res.scala +++ b/test/scaladoc/resources/implicits-base-res.scala @@ -16,8 +16,9 @@ trait MyNumeric[R] * def convToManifestA(x: T) // pimpA7: with 2 constraints: T: Manifest and T <: Double * def convToMyNumericA(x: T) // pimpA6: with a constraint that there is x: MyNumeric[T] implicit in scope * def convToNumericA(x: T) // pimpA1: with a constraint that there is x: Numeric[T] implicit in scope - * def convToPimpedA(x: Bar[Foo[T]]) // pimpA5: no constraints - * def convToPimpedA(x: S) // pimpA4: with 3 constraints: T = Foo[Bar[S]], S: Foo and S: Bar + * def convToPimpedA(x: Bar[Foo[T]]) // pimpA5: no constraints, SHADOWED + * def convToPimpedA(x: S) // pimpA4: with 3 constraints: T = Foo[Bar[S]], S: Foo and S: Bar, SHADOWED + * def convToPimpedA(x: T) // pimpA0: with no constraints, SHADOWED * def convToTraversableOps(x: T) // pimpA7: with 2 constraints: T: Manifest and T <: Double * // should not be abstract! * }}} @@ -52,9 +53,10 @@ object A { * def convToManifestA(x: Double) // pimpA7: no constraints * def convToMyNumericA(x: Double) // pimpA6: (if showAll is set) with a constraint that there is x: MyNumeric[Double] implicit in scope * def convToNumericA(x: Double) // pimpA1: no constraintsd - * def convToPimpedA(x: Bar[Foo[Double]]) // pimpA5: no constraints + * def convToPimpedA(x: Bar[Foo[Double]]) // pimpA5: no constraints, SHADOWED + * def convToPimpedA(x: Double) // pimpA0: no constraints, SHADOWED * def convToTraversableOps(x: Double) // pimpA7: no constraints - * // should not be abstract! + * // should not be abstract! * }}} */ class B extends A[Double] @@ -68,7 +70,8 @@ object B extends A * def convToIntA(x: Int) // pimpA2: no constraints * def convToMyNumericA(x: Int) // pimpA6: (if showAll is set) with a constraint that there is x: MyNumeric[Int] implicit in scope * def convToNumericA(x: Int) // pimpA1: no constraints - * def convToPimpedA(x: Bar[Foo[Int]]) // pimpA5: no constraints + * def convToPimpedA(x: Int) // pimpA0: no constraints, SHADOWED + * def convToPimpedA(x: Bar[Foo[Int]]) // pimpA5: no constraints, SHADOWED * }}} */ class C extends A[Int] @@ -81,7 +84,8 @@ object C extends A * {{{ * def convToMyNumericA(x: String) // pimpA6: (if showAll is set) with a constraint that there is x: MyNumeric[String] implicit in scope * def convToNumericA(x: String) // pimpA1: (if showAll is set) with a constraint that there is x: Numeric[String] implicit in scope - * def convToPimpedA(x: Bar[Foo[String]]) // pimpA5: no constraints + * def convToPimpedA(x: Bar[Foo[String]]) // pimpA5: no constraints, SHADOWED + * def convToPimpedA(x: String) // pimpA0: no constraints, SHADOWED * }}} */ class D extends A[String] diff --git a/test/scaladoc/resources/implicits-elimination-res.scala b/test/scaladoc/resources/implicits-elimination-res.scala index b23667440c..5f7135c9e8 100644 --- a/test/scaladoc/resources/implicits-elimination-res.scala +++ b/test/scaladoc/resources/implicits-elimination-res.scala @@ -2,13 +2,13 @@ * Testing scaladoc implicits elimination */ package scala.test.scaladoc.implicits.elimination { - + import language.implicitConversions // according to SIP18 /** No conversion, as B doesn't bring any member */ class A class B { class C; trait V; type T; } - object A { - implicit def toB(a: A): B = null + object A { + implicit def toB(a: A): B = null } } diff --git a/test/scaladoc/run/diagrams-base.check b/test/scaladoc/run/diagrams-base.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/diagrams-base.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/diagrams-base.scala b/test/scaladoc/run/diagrams-base.scala new file mode 100644 index 0000000000..38bed06502 --- /dev/null +++ b/test/scaladoc/run/diagrams-base.scala @@ -0,0 +1,73 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.diagrams + + import language.implicitConversions + + trait A + trait B + trait C + class E extends A with B with C + object E { implicit def eToT(e: E) = new T } + + class F extends E + class G extends E + private class H extends E /* since it's private, it won't go into the diagram */ + class T { def t = true } + + class X + object X { implicit def xToE(x: X) = new E} + class Y extends X + class Z + object Z { implicit def zToE(z: Z) = new E} + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "-diagrams -implicits" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("diagrams") + val E = base._class("E") + val diag = E.inheritanceDiagram.get + + // there must be a single this node + assert(diag.nodes.filter(_.isThisNode).length == 1) + + // 1. check class E diagram + assert(diag.isClassDiagram) + + val (incoming, outgoing) = diag.edges.partition(!_._1.isThisNode) + assert(incoming.length == 5) + assert(outgoing.head._2.length == 4) + + val (outgoingSuperclass, outgoingImplicit) = outgoing.head._2.partition(_.isNormalNode) + assert(outgoingSuperclass.length == 3) + assert(outgoingImplicit.length == 1) + + val (incomingSubclass, incomingImplicit) = incoming.partition(_._1.isNormalNode) + assert(incomingSubclass.length == 2) + assert(incomingImplicit.length == 3) + + val classDiag = diag.asInstanceOf[ClassDiagram] + assert(classDiag.incomingImplicits.length == 3) + assert(classDiag.outgoingImplicits.length == 1) + + // 2. check package diagram + // NOTE: Z should be eliminated because it's isolated + val packDiag = base.contentDiagram.get + assert(packDiag.isPackageDiagram) + assert(packDiag.nodes.length == 8) // check singular object removal + assert(packDiag.edges.length == 4) + assert(packDiag.edges.foldLeft(0)(_ + _._2.length) == 6) + + // TODO: Should check numbering + } +} \ No newline at end of file diff --git a/test/scaladoc/run/diagrams-determinism.check b/test/scaladoc/run/diagrams-determinism.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/diagrams-determinism.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/diagrams-determinism.scala b/test/scaladoc/run/diagrams-determinism.scala new file mode 100644 index 0000000000..6c8db05d78 --- /dev/null +++ b/test/scaladoc/run/diagrams-determinism.scala @@ -0,0 +1,67 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.diagrams + + trait A + trait B extends A + trait C extends B + trait D extends C with A + trait E extends C with A with D + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "-diagrams -implicits" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + def diagramString(rootPackage: Package) = { + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("diagrams") + val A = base._trait("A") + val B = base._trait("B") + val C = base._trait("C") + val D = base._trait("D") + val E = base._trait("E") + + base.contentDiagram.get.toString + "\n" + + A.inheritanceDiagram.get.toString + "\n" + + B.inheritanceDiagram.get.toString + "\n" + + C.inheritanceDiagram.get.toString + "\n" + + D.inheritanceDiagram.get.toString + "\n" + + E.inheritanceDiagram.get.toString + } + + // 1. check that several runs produce the same output + val run0 = diagramString(rootPackage) + val run1 = diagramString(model.getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")}).rootPackage) + val run2 = diagramString(model.getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")}).rootPackage) + val run3 = diagramString(model.getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")}).rootPackage) + + // any variance in the order of the diagram elements should crash the following tests: + assert(run0 == run1) + assert(run1 == run2) + assert(run2 == run3) + + // 2. check the order in the diagram: this node, subclasses, and then implicit conversions + def assertRightOrder(diagram: Diagram) = { + for ((node, subclasses) <- diagram.edges) + assert(subclasses == subclasses.filter(_.isThisNode) ::: + subclasses.filter(_.isNormalNode) ::: + subclasses.filter(_.isImplicitNode)) + } + + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("diagrams") + assertRightOrder(base.contentDiagram.get) + assertRightOrder(base._trait("A").inheritanceDiagram.get) + assertRightOrder(base._trait("B").inheritanceDiagram.get) + assertRightOrder(base._trait("C").inheritanceDiagram.get) + assertRightOrder(base._trait("D").inheritanceDiagram.get) + assertRightOrder(base._trait("E").inheritanceDiagram.get) + } +} \ No newline at end of file diff --git a/test/scaladoc/run/diagrams-filtering.check b/test/scaladoc/run/diagrams-filtering.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/diagrams-filtering.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/diagrams-filtering.scala b/test/scaladoc/run/diagrams-filtering.scala new file mode 100644 index 0000000000..dfde5cac52 --- /dev/null +++ b/test/scaladoc/run/diagrams-filtering.scala @@ -0,0 +1,93 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc + + /** @contentDiagram hideNodes "scala.test.*.A" "java.*", hideEdges ("*G" -> "*E") */ + package object diagrams { + def foo = 4 + } + + package diagrams { + import language.implicitConversions + + /** @inheritanceDiagram hideIncomingImplicits, hideNodes "*E" */ + trait A + trait AA extends A + trait B + trait AAA extends B + + /** @inheritanceDiagram hideDiagram */ + trait C + trait AAAA extends C + + /** @inheritanceDiagram hideEdges("*E" -> "*A") */ + class E extends A with B with C + class F extends E + /** @inheritanceDiagram hideNodes "*G" "G" */ + class G extends E + private class H extends E /* since it's private, it won't go into the diagram */ + class T { def t = true } + object E { + implicit def eToT(e: E) = new T + implicit def eToA(e: E) = new A { } + } + } + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "-diagrams -implicits" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // base package + // Assert we have 7 nodes and 6 edges + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("diagrams") + val packDiag = base.contentDiagram.get + assert(packDiag.nodes.length == 6) + assert(packDiag.edges.map(_._2.length).sum == 5) + + // trait A + // Assert we have just 2 nodes and 1 edge + val A = base._trait("A") + val ADiag = A.inheritanceDiagram.get + assert(ADiag.nodes.length == 2) + assert(ADiag.edges.map(_._2.length).sum == 1) + + // trait C + val C = base._trait("C") + assert(!C.inheritanceDiagram.isDefined) + + // trait G + val G = base._trait("G") + assert(!G.inheritanceDiagram.isDefined) + + // trait E + val E = base._class("E") + val EDiag = E.inheritanceDiagram.get + + // there must be a single this node + assert(EDiag.nodes.filter(_.isThisNode).length == 1) + + // 1. check class E diagram + val (incoming, outgoing) = EDiag.edges.partition(!_._1.isThisNode) + assert(incoming.length == 2) // F and G + assert(outgoing.head._2.length == 3) // B, C and T + + val (outgoingSuperclass, outgoingImplicit) = outgoing.head._2.partition(_.isNormalNode) + assert(outgoingSuperclass.length == 2) // B and C + assert(outgoingImplicit.length == 1) // T + + val (incomingSubclass, incomingImplicit) = incoming.partition(_._1.isNormalNode) + assert(incomingSubclass.length == 2) // F and G + assert(incomingImplicit.length == 0) + + assert(EDiag.nodes.length == 6) // E, B and C, F and G and the implicit conversion to T + } +} \ No newline at end of file diff --git a/test/scaladoc/run/implicits-ambiguating.check b/test/scaladoc/run/implicits-ambiguating.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/implicits-ambiguating.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/implicits-ambiguating.scala b/test/scaladoc/run/implicits-ambiguating.scala new file mode 100644 index 0000000000..1420593b74 --- /dev/null +++ b/test/scaladoc/run/implicits-ambiguating.scala @@ -0,0 +1,114 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + // test a file instead of a piece of code + override def resourceFile = "implicits-ambiguating-res.scala" + + // start implicits + def scaladocSettings = "-implicits" + + def testModel(root: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + def isAmbiguous(mbr: MemberEntity): Boolean = + mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isAmbiguous).getOrElse(false)).getOrElse(false) + + // SEE THE test/resources/implicits-chaining-res.scala FOR THE EXPLANATION OF WHAT'S CHECKED HERE: + val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._object("ambiguating") + var conv1: ImplicitConversion = null + var conv2: ImplicitConversion = null + +//// class A /////////////////////////////////////////////////////////////////////////////////////////////////////////// + + val A = base._class("A") + + conv1 = A._conversion(base._object("A").qualifiedName + ".AtoX") + conv2 = A._conversion(base._object("A").qualifiedName + ".AtoZ") + assert(conv1.members.length == 11) + assert(conv2.members.length == 11) + assert(conv1.constraints.length == 0) + assert(conv2.constraints.length == 0) + + /** - conv1-5 should be ambiguous + * - conv6-7 should not be ambiguous + * - conv8 should be ambiguous + * - conv9 should be ambiguous + * - conv10 and conv11 should not be ambiguous */ + def check1to9(cls: String): Unit = { + for (conv <- (1 to 5).map("conv" + _)) { + assert(isAmbiguous(conv1._member(conv)), cls + " - AtoX." + conv + " is ambiguous") + assert(isAmbiguous(conv2._member(conv)), cls + " - AtoZ." + conv + " is ambiguous") + } + for (conv <- (6 to 7).map("conv" + _)) { + assert(!isAmbiguous(conv1._member(conv)), cls + " - AtoX." + conv + " is not ambiguous") + assert(!isAmbiguous(conv2._member(conv)), cls + " - AtoZ." + conv + " is not ambiguous") + } + assert(isAmbiguous(conv1._member("conv8")), cls + " - AtoX.conv8 is ambiguous") + assert(isAmbiguous(conv2._member("conv8")), cls + " - AtoZ.conv8 is ambiguous") + assert(isAmbiguous(conv1._member("conv9")), cls + " - AtoX.conv9 is ambiguous") + assert(isAmbiguous(conv2._member("conv9")), cls + " - AtoZ.conv9 is ambiguous") + } + check1to9("A") + assert(!isAmbiguous(conv1._member("conv10")), "A - AtoX.conv10 is not ambiguous") + assert(!isAmbiguous(conv2._member("conv10")), "A - AtoZ.conv10 is not ambiguous") + assert(!isAmbiguous(conv1._member("conv11")), "A - AtoX.conv11 is not ambiguous") + assert(!isAmbiguous(conv2._member("conv11")), "A - AtoZ.conv11 is not ambiguous") + +//// class B /////////////////////////////////////////////////////////////////////////////////////////////////////////// + + val B = base._class("B") + + conv1 = B._conversion(base._object("A").qualifiedName + ".AtoX") + conv2 = B._conversion(base._object("A").qualifiedName + ".AtoZ") + assert(conv1.members.length == 11) + assert(conv2.members.length == 11) + assert(conv1.constraints.length == 0) + assert(conv2.constraints.length == 0) + + /** conv1-9 should be the same, conv10 should be ambiguous, conv11 should be okay */ + check1to9("B") + assert(isAmbiguous(conv1._member("conv10")), "B - AtoX.conv10 is ambiguous") + assert(isAmbiguous(conv2._member("conv10")), "B - AtoZ.conv10 is ambiguous") + assert(!isAmbiguous(conv1._member("conv11")), "B - AtoX.conv11 is not ambiguous") + assert(!isAmbiguous(conv2._member("conv11")), "B - AtoZ.conv11 is not ambiguous") + +//// class C /////////////////////////////////////////////////////////////////////////////////////////////////////////// + + val C = base._class("C") + + conv1 = C._conversion(base._object("A").qualifiedName + ".AtoX") + conv2 = C._conversion(base._object("A").qualifiedName + ".AtoZ") + assert(conv1.members.length == 11) + assert(conv2.members.length == 11) + assert(conv1.constraints.length == 0) + assert(conv2.constraints.length == 0) + + /** conv1-9 should be the same, conv10 and conv11 should not be ambiguous */ + check1to9("C") + assert(!isAmbiguous(conv1._member("conv10")), "C - AtoX.conv10 is not ambiguous") + assert(!isAmbiguous(conv2._member("conv10")), "C - AtoZ.conv10 is not ambiguous") + assert(!isAmbiguous(conv1._member("conv11")), "C - AtoX.conv11 is not ambiguous") + assert(!isAmbiguous(conv2._member("conv11")), "C - AtoZ.conv11 is not ambiguous") + +//// class D /////////////////////////////////////////////////////////////////////////////////////////////////////////// + + val D = base._class("D") + + conv1 = D._conversion(base._object("A").qualifiedName + ".AtoX") + conv2 = D._conversion(base._object("A").qualifiedName + ".AtoZ") + assert(conv1.members.length == 11) + assert(conv2.members.length == 11) + assert(conv1.constraints.length == 0) + assert(conv2.constraints.length == 0) + + /** conv1-9 should be the same, conv10 should not be ambiguous while conv11 should be ambiguous */ + check1to9("D") + assert(!isAmbiguous(conv1._member("conv10")), "D - AtoX.conv10 is not ambiguous") + assert(!isAmbiguous(conv2._member("conv10")), "D - AtoZ.conv10 is not ambiguous") + assert(isAmbiguous(conv1._member("conv11")), "D - AtoX.conv11 is ambiguous") + assert(isAmbiguous(conv2._member("conv11")), "D - AtoZ.conv11 is ambiguous") + } +} \ No newline at end of file diff --git a/test/scaladoc/run/implicits-base.scala b/test/scaladoc/run/implicits-base.scala index 06d017ed70..3d57306f5d 100644 --- a/test/scaladoc/run/implicits-base.scala +++ b/test/scaladoc/run/implicits-base.scala @@ -14,6 +14,9 @@ object Test extends ScaladocModelTest { // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) import access._ + def isShadowed(mbr: MemberEntity): Boolean = + mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isShadowed).getOrElse(false)).getOrElse(false) + // SEE THE test/resources/implicits-base-res.scala FOR THE EXPLANATION OF WHAT'S CHECKED HERE: val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._package("base") var conv: ImplicitConversion = null @@ -22,8 +25,12 @@ object Test extends ScaladocModelTest { val A = base._class("A") - // the method pimped on by pimpA0 should be shadowed by the method in class A - assert(A._conversions(A.qualifiedName + ".pimpA0").isEmpty) + // def convToPimpedA(x: T) // pimpA0: with no constraints, SHADOWED + conv = A._conversion(A.qualifiedName + ".pimpA0") + assert(conv.members.length == 1) + assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) + assert(conv._member("convToPimpedA").resultType.name == "T") // def convToNumericA: T // pimpA1: with a constraint that there is x: Numeric[T] implicit in scope conv = A._conversion(A.qualifiedName + ".pimpA1") @@ -53,6 +60,7 @@ object Test extends ScaladocModelTest { conv = A._conversion(A.qualifiedName + ".pimpA5") assert(conv.members.length == 1) assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) assert(conv._member("convToPimpedA").resultType.name == "Bar[Foo[T]]") // def convToMyNumericA: T // pimpA6: with a constraint that there is x: MyNumeric[T] implicit in scope @@ -76,10 +84,16 @@ object Test extends ScaladocModelTest { val B = base._class("B") // these conversions should not affect B - assert(B._conversions(A.qualifiedName + ".pimpA0").isEmpty) assert(B._conversions(A.qualifiedName + ".pimpA2").isEmpty) assert(B._conversions(A.qualifiedName + ".pimpA4").isEmpty) + // def convToPimpedA(x: Double) // pimpA0: no constraints, SHADOWED + conv = B._conversion(A.qualifiedName + ".pimpA0") + assert(conv.members.length == 1) + assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) + assert(conv._member("convToPimpedA").resultType.name == "Double") + // def convToNumericA: Double // pimpA1: no constraintsd conv = B._conversion(A.qualifiedName + ".pimpA1") assert(conv.members.length == 1) @@ -96,6 +110,7 @@ object Test extends ScaladocModelTest { conv = B._conversion(A.qualifiedName + ".pimpA5") assert(conv.members.length == 1) assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) assert(conv._member("convToPimpedA").resultType.name == "Bar[Foo[Double]]") // def convToMyNumericA: Double // pimpA6: (if showAll is set) with a constraint that there is x: MyNumeric[Double] implicit in scope @@ -119,11 +134,17 @@ object Test extends ScaladocModelTest { val C = base._class("C") // these conversions should not affect C - assert(C._conversions(A.qualifiedName + ".pimpA0").isEmpty) assert(C._conversions(A.qualifiedName + ".pimpA3").isEmpty) assert(C._conversions(A.qualifiedName + ".pimpA4").isEmpty) assert(C._conversions(A.qualifiedName + ".pimpA7").isEmpty) + // def convToPimpedA(x: Int) // pimpA0: no constraints, SHADOWED + conv = C._conversion(A.qualifiedName + ".pimpA0") + assert(conv.members.length == 1) + assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) + assert(conv._member("convToPimpedA").resultType.name == "Int") + // def convToNumericA: Int // pimpA1: no constraints conv = C._conversion(A.qualifiedName + ".pimpA1") assert(conv.members.length == 1) @@ -140,6 +161,7 @@ object Test extends ScaladocModelTest { conv = C._conversion(A.qualifiedName + ".pimpA5") assert(conv.members.length == 1) assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) assert(conv._member("convToPimpedA").resultType.name == "Bar[Foo[Int]]") // def convToMyNumericA: Int // pimpA6: (if showAll is set) with a constraint that there is x: MyNumeric[Int] implicit in scope @@ -153,12 +175,18 @@ object Test extends ScaladocModelTest { val D = base._class("D") // these conversions should not affect D - assert(D._conversions(A.qualifiedName + ".pimpA0").isEmpty) assert(D._conversions(A.qualifiedName + ".pimpA2").isEmpty) assert(D._conversions(A.qualifiedName + ".pimpA3").isEmpty) assert(D._conversions(A.qualifiedName + ".pimpA4").isEmpty) assert(D._conversions(A.qualifiedName + ".pimpA7").isEmpty) + // def convToPimpedA(x: String) // pimpA0: no constraints, SHADOWED + conv = D._conversion(A.qualifiedName + ".pimpA0") + assert(conv.members.length == 1) + assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) + assert(conv._member("convToPimpedA").resultType.name == "String") + // def convToNumericA: String // pimpA1: (if showAll is set) with a constraint that there is x: Numeric[String] implicit in scope conv = D._conversion(A.qualifiedName + ".pimpA1") assert(conv.members.length == 1) @@ -169,6 +197,7 @@ object Test extends ScaladocModelTest { conv = D._conversion(A.qualifiedName + ".pimpA5") assert(conv.members.length == 1) assert(conv.constraints.length == 0) + assert(isShadowed(conv._member("convToPimpedA"))) assert(conv._member("convToPimpedA").resultType.name == "Bar[Foo[String]]") // def convToMyNumericA: String // pimpA6: (if showAll is set) with a constraint that there is x: MyNumeric[String] implicit in scope diff --git a/test/scaladoc/run/implicits-elimination.check b/test/scaladoc/run/implicits-elimination.check deleted file mode 100644 index 619c56180b..0000000000 --- a/test/scaladoc/run/implicits-elimination.check +++ /dev/null @@ -1 +0,0 @@ -Done. diff --git a/test/scaladoc/run/implicits-elimination.scala b/test/scaladoc/run/implicits-elimination.scala deleted file mode 100644 index ed37b9cd90..0000000000 --- a/test/scaladoc/run/implicits-elimination.scala +++ /dev/null @@ -1,23 +0,0 @@ -import scala.tools.nsc.doc.model._ -import scala.tools.partest.ScaladocModelTest -import language._ - -object Test extends ScaladocModelTest { - - // test a file instead of a piece of code - override def resourceFile = "implicits-elimination-res.scala" - - // start implicits - def scaladocSettings = "-implicits" - - def testModel(root: Package) = { - // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) - import access._ - - // SEE THE test/resources/implicits-elimination-res.scala FOR THE EXPLANATION OF WHAT'S CHECKED HERE: - val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._package("elimination") - val A = base._class("A") - - assert(A._conversions(A.qualifiedName + ".toB").isEmpty) - } -} diff --git a/test/scaladoc/run/implicits-shadowing.scala b/test/scaladoc/run/implicits-shadowing.scala index 7835223d21..2827d31122 100644 --- a/test/scaladoc/run/implicits-shadowing.scala +++ b/test/scaladoc/run/implicits-shadowing.scala @@ -13,6 +13,9 @@ object Test extends ScaladocModelTest { // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) import access._ + def isShadowed(mbr: MemberEntity): Boolean = + mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isShadowed).getOrElse(false)).getOrElse(false) + // SEE THE test/resources/implicits-chaining-res.scala FOR THE EXPLANATION OF WHAT'S CHECKED HERE: val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._object("shadowing") var conv: ImplicitConversion = null @@ -22,12 +25,8 @@ object Test extends ScaladocModelTest { val A = base._class("A") conv = A._conversion(base._object("A").qualifiedName + ".AtoZ") - assert(conv.members.length == 5) - conv._member("conv5") - conv._member("conv8") - conv._member("conv9") - conv._member("conv10") - conv._member("conv11") + assert(conv.members.length == 11) + assert(conv.members.forall(isShadowed(_))) assert(conv.constraints.length == 0) //// class B /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -35,11 +34,8 @@ object Test extends ScaladocModelTest { val B = base._class("B") conv = B._conversion(base._object("A").qualifiedName + ".AtoZ") - assert(conv.members.length == 4) - conv._member("conv5") - conv._member("conv8") - conv._member("conv9") - conv._member("conv11") + assert(conv.members.length == 11) + assert(conv.members.forall(isShadowed(_))) assert(conv.constraints.length == 0) //// class C /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -47,12 +43,8 @@ object Test extends ScaladocModelTest { val C = base._class("C") conv = C._conversion(base._object("A").qualifiedName + ".AtoZ") - assert(conv.members.length == 5) - conv._member("conv5") - conv._member("conv8") - conv._member("conv9") - conv._member("conv10") - conv._member("conv11") + assert(conv.members.length == 11) + assert(conv.members.forall(isShadowed(_))) assert(conv.constraints.length == 0) //// class D /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -60,11 +52,8 @@ object Test extends ScaladocModelTest { val D = base._class("D") conv = D._conversion(base._object("A").qualifiedName + ".AtoZ") - assert(conv.members.length == 4) - conv._member("conv5") - conv._member("conv8") - conv._member("conv9") - conv._member("conv10") + assert(conv.members.length == 11) + assert(conv.members.forall(isShadowed(_))) assert(conv.constraints.length == 0) } -} \ No newline at end of file +} diff --git a/test/scaladoc/run/implicits-var-exp.check b/test/scaladoc/run/implicits-var-exp.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/implicits-var-exp.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/implicits-var-exp.scala b/test/scaladoc/run/implicits-var-exp.scala new file mode 100644 index 0000000000..16569fe3c2 --- /dev/null +++ b/test/scaladoc/run/implicits-var-exp.scala @@ -0,0 +1,43 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.variable.expansion { + /** + * Blah blah blah + */ + class A + + object A { + import language.implicitConversions + implicit def aToB(a: A) = new B + } + + /** + * @define coll collection + */ + class B { + /** + * foo returns a $coll + */ + def foo: Nothing = ??? + } + } + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "-implicits" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("variable")._package("expansion") + val foo = base._class("A")._method("foo") + + assert(foo.comment.get.body.toString.contains("foo returns a collection"), "\"" + foo.comment.get.body.toString + "\".contains(\"foo returns a collection\")") + } +} \ No newline at end of file diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala index 68ca68efdd..b576ba5544 100644 --- a/test/scaladoc/scalacheck/CommentFactoryTest.scala +++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala @@ -5,10 +5,12 @@ import scala.tools.nsc.Global import scala.tools.nsc.doc import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ class Factory(val g: Global, val s: doc.Settings) extends doc.model.ModelFactory(g, s) { - thisFactory: Factory with ModelFactoryImplicitSupport with CommentFactory with doc.model.TreeFactory => + thisFactory: Factory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory => def strip(c: Comment): Option[Inline] = { c.body match { @@ -29,7 +31,7 @@ object Test extends Properties("CommentFactory") { val settings = new doc.Settings((str: String) => {}) val reporter = new scala.tools.nsc.reporters.ConsoleReporter(settings) val g = new Global(settings, reporter) - (new Factory(g, settings) with ModelFactoryImplicitSupport with CommentFactory with doc.model.TreeFactory) + (new Factory(g, settings) with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory) } def parse(src: String, dst: Inline) = { -- cgit v1.2.3 From c85b4a495619ca69a5d0c0b69fc30233fbea3dff Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 13 Jun 2012 21:41:06 +0200 Subject: Scaladoc class diagrams part 2 This commit contains the svg diagram generation using the graphviz package, the template changes, the css styling and javascript code that enables displaying and interacting with the diagrams. The full history is located at: https://github.com/damienobrist/scala/tree/feature/diagrams-dev The diagrams are included as svg markup inside the html code. This enables interacting with the image beyond what would be possible with a static image (highlighting, scaling, tooltips, links to nodes, etc). The svg generation has four main phases: model => dot, dot => svg (using the graphviz package), svg postprocessing, inclusion in the html page. This commit also fixes SI-5212 - links to individual pages automatically load the left navigation panel of the website. Commit summary: - diagram generation - model => dot (DotDiagramGenerator.scala, DiagramGenerator.scala) - dot => svg (DotRunner.scala) - svg post-processing (DotDiagramGenerator.scala) - svg inclusion in the html (Template.scala) - diagram interaction - css, js and image files Review by @heathermiller, @kzys. Also fixed the memory leak that was causing the testsuite to timeout. --- .../scala/tools/nsc/doc/html/HtmlFactory.scala | 16 +- .../scala/tools/nsc/doc/html/page/Template.scala | 52 ++- .../doc/html/page/diagram/DiagramGenerator.scala | 53 +++ .../html/page/diagram/DotDiagramGenerator.scala | 470 +++++++++++++++++++++ .../nsc/doc/html/page/diagram/DotRunner.scala | 227 ++++++++++ .../nsc/doc/html/resource/lib/class_diagram.png | Bin 0 -> 3910 bytes .../tools/nsc/doc/html/resource/lib/diagrams.css | 135 ++++++ .../tools/nsc/doc/html/resource/lib/diagrams.js | 324 ++++++++++++++ .../nsc/doc/html/resource/lib/modernizr.custom.js | 4 + .../nsc/doc/html/resource/lib/object_diagram.png | Bin 0 -> 3903 bytes .../tools/nsc/doc/html/resource/lib/raphael-min.js | 10 + .../tools/nsc/doc/html/resource/lib/template.css | 12 +- .../tools/nsc/doc/html/resource/lib/template.js | 18 +- .../nsc/doc/html/resource/lib/trait_diagram.png | Bin 0 -> 3882 bytes 14 files changed, 1297 insertions(+), 24 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala create mode 100644 src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala create mode 100644 src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/class_diagram.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.js create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/modernizr.custom.js create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/object_diagram.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/raphael-min.js create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/trait_diagram.png diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala index 914824d523..51c5793d46 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -11,6 +11,9 @@ import model._ import java.io.{ File => JFile } import io.{ Streamable, Directory } import scala.collection._ +import page.diagram._ + +import html.page.diagram.DiagramGenerator /** A class that can generate Scaladoc sites to some fixed root folder. * @author David Bernard @@ -29,21 +32,27 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { "jquery.js", "jquery.layout.js", "scheduler.js", + "diagrams.js", "template.js", "tools.tooltip.js", + "modernizr.custom.js", "index.css", "ref-index.css", "template.css", + "diagrams.css", "class.png", "class_big.png", + "class_diagram.png", "object.png", "object_big.png", + "object_diagram.png", "package.png", "package_big.png", "trait.png", "trait_big.png", + "trait_diagram.png", "class_to_object_big.png", "object_to_class_big.png", @@ -105,6 +114,8 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { finally out.close() } + DiagramGenerator.initialize(universe.settings) + libResources foreach (s => copyResource("lib/" + s)) new page.Index(universe, index) writeFor this @@ -115,14 +126,17 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { for (letter <- index.firstLetterIndex) { new html.page.ReferenceIndex(letter._1, index, universe) writeFor this } + + DiagramGenerator.cleanup() } def writeTemplates(writeForThis: HtmlPage => Unit) { val written = mutable.HashSet.empty[DocTemplateEntity] + val diagramGenerator: DiagramGenerator = new DotDiagramGenerator(universe.settings) def writeTemplate(tpl: DocTemplateEntity) { if (!(written contains tpl)) { - writeForThis(new page.Template(universe, tpl)) + writeForThis(new page.Template(universe, diagramGenerator, tpl)) written += tpl tpl.templates map writeTemplate } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 5978489585..24cff1b475 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -8,12 +8,18 @@ package doc package html package page +import model._ +import model.diagram._ +import diagram._ + import scala.xml.{ NodeSeq, Text, UnprefixedAttribute } import language.postfixOps import model._ +import model.diagram._ +import diagram._ -class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { +class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemplateEntity) extends HtmlPage { val path = templateToPath(tpl) @@ -30,10 +36,22 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage val headers = - + + + { if (universe.settings.docDiagrams.isSetByUser) { + + + } else NodeSeq.Empty } + val valueMembers = @@ -614,7 +632,19 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage case _ => NodeSeq.Empty } - memberComment ++ paramComments ++ attributesBlock ++ linearization ++ subclasses + val typeHierarchy = if (s.docDiagrams.isSetByUser) mbr match { + case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.inheritanceDiagram.isDefined => + makeDiagramHtml(dtpl, dtpl.inheritanceDiagram, "Type Hierarchy", "inheritance-diagram") + case _ => NodeSeq.Empty + } else NodeSeq.Empty // diagrams not generated + + val contentHierarchy = if (s.docDiagrams.isSetByUser) mbr match { + case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.contentDiagram.isDefined => + makeDiagramHtml(dtpl, dtpl.contentDiagram, "Content Hierarchy", "content-diagram") + case _ => NodeSeq.Empty + } else NodeSeq.Empty // diagrams not generated + + memberComment ++ paramComments ++ attributesBlock ++ linearization ++ subclasses ++ typeHierarchy ++ contentHierarchy } def kindToString(mbr: MemberEntity): String = { @@ -920,9 +950,25 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage xml.Text(ub.typeParamName + " is a subclass of " + ub.upperBound.name + " (" + ub.typeParamName + " <: ") ++ typeToHtml(ub.upperBound, true) ++ xml.Text(")") } + + def makeDiagramHtml(tpl: DocTemplateEntity, diagram: Option[Diagram], description: String, id: String) = { + val s = universe.settings + val diagramSvg = generator.generate(diagram.get, tpl, this) + if (diagramSvg != NodeSeq.Empty) { +
    + { description } + Learn more about scaladoc diagrams +
    { + diagramSvg + }
    +
    + } else NodeSeq.Empty + } } object Template { + /* Vlad: Lesson learned the hard way: don't put any stateful code that references the model here, + * it won't be garbage collected and you'll end up filling the heap with garbage */ def isImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined def isShadowedImplicit(mbr: MemberEntity): Boolean = diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala new file mode 100644 index 0000000000..61c1819d11 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramGenerator.scala @@ -0,0 +1,53 @@ +/** + * @author Damien Obrist + * @author Vlad Ureche + */ +package scala.tools.nsc +package doc +package html +package page +package diagram + +import scala.xml.NodeSeq +import scala.tools.nsc.doc.html.HtmlPage +import scala.tools.nsc.doc.model.diagram.Diagram +import scala.tools.nsc.doc.model.DocTemplateEntity + +trait DiagramGenerator { + + /** + * Generates a visualization of the internal representation + * of a diagram. + * + * @param d The model of the diagram + * @param p The page the diagram will be embedded in (needed for link generation) + * @return The HTML to be embedded in the Scaladoc page + */ + def generate(d: Diagram, t: DocTemplateEntity, p: HtmlPage):NodeSeq +} + +object DiagramGenerator { + + // TODO: This is tailored towards the dot generator, since it's the only generator. In the future it should be more + // general. + + private[this] var dotRunner: DotRunner = null + private[this] var settings: doc.Settings = null + + def initialize(s: doc.Settings) = + settings = s + + def getDotRunner() = { + if (dotRunner == null) + dotRunner = new DotRunner(settings) + dotRunner + } + + def cleanup() = { + DiagramStats.printStats(settings) + if (dotRunner != null) { + dotRunner.cleanup() + dotRunner = null + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..f5a1d1b088 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -0,0 +1,470 @@ +/** + * @author Damien Obrist + * @author Vlad Ureche + */ +package scala.tools.nsc +package doc +package html +package page +package diagram + +import scala.xml.{NodeSeq, XML, PrefixedAttribute, Elem, MetaData, Null, UnprefixedAttribute} +import scala.collection.immutable._ +import javax.xml.parsers.SAXParser +import model._ +import model.diagram._ + +class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { + + // the page where the diagram will be embedded + private var page: HtmlPage = null + // path to the "lib" folder relative to the page + private var pathToLib: String = null + // maps nodes to unique indices + private var node2Index: Map[Node, Int] = null + // maps an index to its corresponding node + private var index2Node: Map[Int, Node] = null + // true if the current diagram is a class diagram + private var isClassDiagram = false + // incoming implicit nodes (needed for determining the CSS class of a node) + private var incomingImplicitNodes: List[Node] = List() + // the suffix used when there are two many classes to show + private final val MultiSuffix = " classes/traits" + // used to generate unique node and edge ids (i.e. avoid conflicts with multiple diagrams) + private var counter = 0 + + def generate(diagram: Diagram, template: DocTemplateEntity, page: HtmlPage):NodeSeq = { + counter = counter + 1; + this.page = page + pathToLib = "../" * (page.templateToPath(template).size - 1) + "lib/" + val dot = generateDot(diagram) + val result = generateSVG(dot, template) + // clean things up a bit, so we don't leave garbage on the heap + this.page = null + node2Index = null + index2Node = null + incomingImplicitNodes = List() + result + } + + /** + * Generates a dot string for a given diagram. + */ + private def generateDot(d: Diagram) = { + // inheritance nodes (all nodes except thisNode and implicit nodes) + var nodes: List[Node] = null + // inheritance edges (all edges except implicit edges) + var edges: List[(Node, List[Node])] = null + + // timing + var tDot = -System.currentTimeMillis + + // variables specific to class diagrams: + // current node of a class diagram + var thisNode:Node = null + var subClasses = List[Node]() + var superClasses = List[Node]() + var incomingImplicits = List[Node]() + var outgoingImplicits = List[Node]() + var subClassesTooltip: Option[String] = None + var superClassesTooltip: Option[String] = None + var incomingImplicitsTooltip: Option[String] = None + var outgoingImplicitsTooltip: Option[String] = None + isClassDiagram = false + + d match { + case ClassDiagram(_thisNode, _superClasses, _subClasses, _incomingImplicits, _outgoingImplicits) => + + def textTypeEntity(text: String) = + new TypeEntity { + val name = text + def refEntity: SortedMap[Int, (TemplateEntity, Int)] = SortedMap() + } + + // it seems dot chokes on node names over 8000 chars, so let's limit the size of the string + // conservatively, we'll limit at 4000, to be sure: + def limitSize(str: String) = if (str.length > 4000) str.substring(0, 3996) + " ..." else str + + // avoid overcrowding the diagram: + // if there are too many super / sub / implicit nodes, represent + // them by on node with a corresponding tooltip + superClasses = if (_superClasses.length > settings.docDiagramsMaxNormalClasses.value) { + superClassesTooltip = Some(limitSize(_superClasses.map(_.tpe.name).mkString(", "))) + List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None)) + } else _superClasses + + subClasses = if (_subClasses.length > settings.docDiagramsMaxNormalClasses.value) { + subClassesTooltip = Some(limitSize(_subClasses.map(_.tpe.name).mkString(", "))) + List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None)) + } else _subClasses + + incomingImplicits = if (_incomingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { + incomingImplicitsTooltip = Some(limitSize(_incomingImplicits.map(_.tpe.name).mkString(", "))) + List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None)) + } else _incomingImplicits + + outgoingImplicits = if (_outgoingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { + outgoingImplicitsTooltip = Some(limitSize(_outgoingImplicits.map(_.tpe.name).mkString(", "))) + List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None)) + } else _outgoingImplicits + + thisNode = _thisNode + nodes = List() + edges = (thisNode -> superClasses) :: subClasses.map(_ -> List(thisNode)) + node2Index = (thisNode::subClasses:::superClasses:::incomingImplicits:::outgoingImplicits).zipWithIndex.toMap + isClassDiagram = true + incomingImplicitNodes = incomingImplicits + case _ => + nodes = d.nodes + edges = d.edges + node2Index = d.nodes.zipWithIndex.toMap + incomingImplicitNodes = List() + } + index2Node = node2Index map {_.swap} + + val implicitsDot = { + if (!isClassDiagram) "" + else { + // dot cluster containing thisNode + val thisCluster = "subgraph clusterThis {\n" + + "style=\"invis\"\n" + + node2Dot(thisNode, None) + + "}" + // dot cluster containing incoming implicit nodes, if any + val incomingCluster = { + if(incomingImplicits.isEmpty) "" + else "subgraph clusterIncoming {\n" + + "style=\"invis\"\n" + + incomingImplicits.reverse.map(n => node2Dot(n, incomingImplicitsTooltip)).mkString + + (if (incomingImplicits.size > 1) + incomingImplicits.map(n => "node" + node2Index(n)).mkString(" -> ") + + " [constraint=\"false\", style=\"invis\", minlen=\"0.0\"];\n" + else "") + + "}" + } + // dot cluster containing outgoing implicit nodes, if any + val outgoingCluster = { + if(outgoingImplicits.isEmpty) "" + else "subgraph clusterOutgoing {\n" + + "style=\"invis\"\n" + + outgoingImplicits.reverse.map(n => node2Dot(n, outgoingImplicitsTooltip)).mkString + + (if (outgoingImplicits.size > 1) + outgoingImplicits.map(n => "node" + node2Index(n)).mkString(" -> ") + + " [constraint=\"false\", style=\"invis\", minlen=\"0.0\"];\n" + else "") + + "}" + } + + // assemble clusters into another cluster + val incomingTooltip = incomingImplicits.map(_.name).mkString(", ") + " can be implicitly converted to " + thisNode.name + val outgoingTooltip = thisNode.name + " can be implicitly converted to " + outgoingImplicits.map(_.name).mkString(", ") + "subgraph clusterAll {\n" + + "style=\"invis\"\n" + + outgoingCluster + "\n" + + thisCluster + "\n" + + incomingCluster + "\n" + + // incoming implicit edge + (if (!incomingImplicits.isEmpty) { + val n = incomingImplicits.last + "node" + node2Index(n) +" -> node" + node2Index(thisNode) + + " [id=\"" + cssClass(n, thisNode) + "|" + node2Index(n) + "_" + node2Index(thisNode) + "\", tooltip=\"" + incomingTooltip + "\"" + + ", constraint=\"false\", minlen=\"2\", ltail=\"clusterIncoming\", lhead=\"clusterThis\", label=\"implicitly\"];\n" + } else "") + + // outgoing implicit edge + (if (!outgoingImplicits.isEmpty) { + val n = outgoingImplicits.head + "node" + node2Index(thisNode) + " -> node" + node2Index(n) + + " [id=\"" + cssClass(thisNode, n) + "|" + node2Index(thisNode) + "_" + node2Index(n) + "\", tooltip=\"" + outgoingTooltip + "\"" + + ", constraint=\"false\", minlen=\"2\", ltail=\"clusterThis\", lhead=\"clusterOutgoing\", label=\"implicitly\"];\n" + } else "") + + "}" + } + } + + // assemble graph + val graph = "digraph G {\n" + + // graph / node / edge attributes + graphAttributesStr + + "node [" + nodeAttributesStr + "];\n" + + "edge [" + edgeAttributesStr + "];\n" + + implicitsDot + "\n" + + // inheritance nodes + nodes.map(n => node2Dot(n, None)).mkString + + subClasses.map(n => node2Dot(n, subClassesTooltip)).mkString + + superClasses.map(n => node2Dot(n, superClassesTooltip)).mkString + + // inheritance edges + edges.map{ case (from, tos) => tos.map(to => { + val id = "graph" + counter + "_" + node2Index(to) + "_" + node2Index(from) + // the X -> Y edge is inverted twice to keep the diagram flowing the right way + // that is, an edge from node X to Y will result in a dot instruction nodeY -> nodeX [dir="back"] + "node" + node2Index(to) + " -> node" + node2Index(from) + + " [id=\"" + cssClass(to, from) + "|" + id + "\", " + + "tooltip=\"" + from.name + (if (from.name.endsWith(MultiSuffix)) " are subtypes of " else " is a subtype of ") + + to.name + "\", dir=\"back\", arrowtail=\"empty\"];\n" + }).mkString}.mkString + + "}" + + tDot += System.currentTimeMillis + DiagramStats.addDotGenerationTime(tDot) + + graph + } + + /** + * Generates the dot string of a given node. + */ + private def node2Dot(node: Node, tooltip: Option[String]) = { + + // escape HTML characters in node names + def escape(name: String) = name.replace("&", "&").replace("<", "<").replace(">", ">"); + + // assemble node attribues in a map + var attr = scala.collection.mutable.Map[String, String]() + + // link + node.doctpl match { + case Some(tpl) => attr += "URL" -> (page.relativeLinkTo(tpl) + "#inheritance-diagram") + case _ => + } + + // tooltip + tooltip match { + case Some(text) => attr += "tooltip" -> text + // show full name where available (instead of TraversableOps[A] show scala.collection.parallel.TraversableOps[A]) + case None if node.tpl.isDefined => attr += "tooltip" -> node.tpl.get.qualifiedName + case _ => + } + + // styles + if(node.isImplicitNode) + attr ++= implicitStyle + else if(node.isOutsideNode) + attr ++= outsideStyle + else if(node.isTraitNode) + attr ++= traitStyle + else if(node.isClassNode) + attr ++= classStyle + else if(node.isObjectNode) + attr ++= objectStyle + else + attr ++= defaultStyle + + // HTML label + var name = escape(node.name) + var img = "" + if(node.isTraitNode) + img = "trait_diagram.png" + else if(node.isClassNode) + img = "class_diagram.png" + else if(node.isObjectNode) + img = "object_diagram.png" + + if(!img.equals("")) { + img = "" + name = name + " " + } + val label = "<" + + "" + img + "" + + "
    " + name + "
    >" + + // dot does not allow to specify a CSS class, therefore + // set the id to "{class}|{id}", which will be used in + // the transform method + val id = "graph" + counter + "_" + node2Index(node) + attr += ("id" -> (cssClass(node) + "|" + id)) + + // return dot string + "node" + node2Index(node) + " [label=" + label + "," + flatten(attr.toMap) + "];\n" + } + + /** + * Returns the CSS class for an edge connecting node1 and node2. + */ + private def cssClass(node1: Node, node2: Node): String = { + if (node1.isImplicitNode && node2.isThisNode) + "implicit-incoming" + else if (node1.isThisNode && node2.isImplicitNode) + "implicit-outgoing" + else + "inheritance" + } + + /** + * Returns the CSS class for a node. + */ + private def cssClass(node: Node): String = + if (node.isImplicitNode && incomingImplicitNodes.contains(node)) + "implicit-incoming" + else if (node.isImplicitNode) + "implicit-outgoing" + else if (node.isObjectNode) + "object" + else if (node.isThisNode) + "this" + cssBaseClass(node, "") + else if (node.isOutsideNode) + "outside" + cssBaseClass(node, "") + else + cssBaseClass(node, "default") + + private def cssBaseClass(node: Node, default: String) = + if (node.isClassNode) + " class" + else if (node.isTraitNode) + " trait" + else if (node.isObjectNode) + " trait" + else + default + + /** + * Calls dot with a given dot string and returns the SVG output. + */ + private def generateSVG(dotInput: String, template: DocTemplateEntity) = { + val dotOutput = DiagramGenerator.getDotRunner.feedToDot(dotInput, template) + var tSVG = -System.currentTimeMillis + + val result = if (dotOutput != null) { + val src = scala.io.Source.fromString(dotOutput); + try { + val cpa = scala.xml.parsing.ConstructingParser.fromSource(src, false) + val doc = cpa.document() + if (doc != null) + transform(doc.docElem) + else + NodeSeq.Empty + } catch { + case exc => + if (settings.docDiagramsDebug.value) { + settings.printMsg("\n\n**********************************************************************") + settings.printMsg("Encountered an error while generating page for " + template.qualifiedName) + settings.printMsg(dotInput.toString.split("\n").mkString("\nDot input:\n\t","\n\t","")) + settings.printMsg(dotOutput.toString.split("\n").mkString("\nDot output:\n\t","\n\t","")) + settings.printMsg(exc.getStackTrace.mkString("\nException: " + exc.toString + ":\n\tat ", "\n\tat ","")) + settings.printMsg("\n\n**********************************************************************") + } else { + settings.printMsg("\nThe diagram for " + template.qualifiedName + " could not be created due to an internal error.") + settings.printMsg("Use " + settings.docDiagramsDebug.name + " for more information and please file this as a bug.") + } + NodeSeq.Empty + } + } else + NodeSeq.Empty + + tSVG += System.currentTimeMillis + DiagramStats.addSvgTime(tSVG) + + result + } + + /** + * Transforms the SVG generated by dot: + * - adds a class attribute to the SVG element + * - changes the path of the node images from absolute to relative + * - assigns id and class attributes to nodes and edges + * - removes title elements + */ + private def transform(e:scala.xml.Node): scala.xml.Node = e match { + // add an id and class attribute to the SVG element + case Elem(prefix, "svg", attribs, scope, child @ _*) => { + val klass = if (isClassDiagram) "class-diagram" else "package-diagram" + Elem(prefix, "svg", attribs, scope, child map(x => transform(x)) : _*) % + new UnprefixedAttribute("id", "graph" + counter, Null) % + new UnprefixedAttribute("class", klass, Null) + } + // change the path of the node images from absolute to relative + case img @ => { + val href = (img \ "@{http://www.w3.org/1999/xlink}href").toString + val file = href.substring(href.lastIndexOf("/") + 1, href.size) + img.asInstanceOf[Elem] % + new PrefixedAttribute("xlink", "href", pathToLib + file, Null) + } + // assign id and class attributes to edges and nodes: + // the id attribute generated by dot has the format: "{class}|{id}" + case g @ Elem(prefix, "g", attribs, scope, children @ _*) if (List("edge", "node").contains((g \ "@class").toString)) => { + val res = new Elem(prefix, "g", attribs, scope, (children map(x => transform(x))): _*) + val dotId = (g \ "@id").toString + if (dotId.count(_ == '|') == 1) { + val Array(klass, id) = dotId.toString.split("\\|") + res % new UnprefixedAttribute("id", id, Null) % + new UnprefixedAttribute("class", (g \ "@class").toString + " " + klass, Null) + } + else res + } + // remove titles + case { _* } => + scala.xml.Text("") + // apply recursively + case Elem(prefix, label, attribs, scope, child @ _*) => + Elem(prefix, label, attribs, scope, child map(x => transform(x)) : _*) + case x => x + } + + /* graph / node / edge attributes */ + + private val graphAttributes: Map[String, String] = Map( + "compound" -> "true", + "rankdir" -> "TB" + ) + + private val nodeAttributes = Map( + "shape" -> "rectangle", + "style" -> "filled", + "penwidth" -> "1", + "margin" -> "0.08,0.01", + "width" -> "0.0", + "height" -> "0.0", + "fontname" -> "Arial", + "fontsize" -> "10.00" + ) + + private val edgeAttributes = Map( + "color" -> "#d4d4d4", + "arrowsize" -> "0.5", + "fontcolor" -> "#aaaaaa", + "fontsize" -> "10.00", + "fontname" -> "Arial" + ) + + private val defaultStyle = Map( + "color" -> "#ababab", + "fillcolor" -> "#e1e1e1", + "fontcolor" -> "#7d7d7d", + "margin" -> "0.1,0.04" + ) + + private val implicitStyle = Map( + "color" -> "#ababab", + "fillcolor" -> "#e1e1e1", + "fontcolor" -> "#7d7d7d" + ) + + private val outsideStyle = Map( + "color" -> "#ababab", + "fillcolor" -> "#e1e1e1", + "fontcolor" -> "#7d7d7d" + ) + + private val traitStyle = Map( + "color" -> "#37657D", + "fillcolor" -> "#498AAD", + "fontcolor" -> "#ffffff" + ) + + private val classStyle = Map( + "color" -> "#115F3B", + "fillcolor" -> "#0A955B", + "fontcolor" -> "#ffffff" + ) + + private val objectStyle = Map( + "color" -> "#102966", + "fillcolor" -> "#3556a7", + "fontcolor" -> "#ffffff" + ) + + private def flatten(attributes: Map[String, String]) = attributes.map{ case (key, value) => key + "=\"" + value + "\"" }.mkString(", ") + + private val graphAttributesStr = graphAttributes.map{ case (key, value) => key + "=\"" + value + "\";\n" }.mkString + private val nodeAttributesStr = flatten(nodeAttributes) + private val edgeAttributesStr = flatten(edgeAttributes) +} \ No newline at end of file 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 new file mode 100644 index 0000000000..37600fa908 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala @@ -0,0 +1,227 @@ +package scala.tools.nsc +package doc +package html +package page +package diagram + +import java.io.InputStream +import java.io.OutputStream +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.io.BufferedWriter +import java.io.BufferedReader +import java.io.IOException +import scala.sys.process._ +import scala.concurrent.SyncVar + +import model._ +import model.diagram._ + +/** This class takes care of running the graphviz dot utility */ +class DotRunner(settings: doc.Settings) { + + private[this] var dotRestarts = 0 + private[this] var dotProcess: DotProcess = null + + def feedToDot(dotInput: String, template: DocTemplateEntity): String = { + + if (dotProcess == null) { + if (dotRestarts < settings.docDiagramsDotRestart.value) { + if (dotRestarts != 0) + settings.printMsg("A new graphviz dot process will be created...\n") + dotRestarts += 1 + dotProcess = new DotProcess(settings) + } else + return null + } + + val tStart = System.currentTimeMillis + val result = dotProcess.feedToDot(dotInput, template.qualifiedName) + val tFinish = System.currentTimeMillis + DiagramStats.addDotRunningTime(tFinish - tStart) + + if (result == null) { + dotProcess.cleanup() + dotProcess = null + if (dotRestarts == settings.docDiagramsDotRestart.value) { + settings.printMsg("\n") + settings.printMsg("**********************************************************************") + settings.printMsg("Diagrams will be disabled for this run beucause the graphviz dot tool") + settings.printMsg("has malfunctioned too many times. These scaladoc flags may help:") + settings.printMsg("") + val baseList = List(settings.docDiagramsDebug, + settings.docDiagramsDotPath, + settings.docDiagramsDotRestart, + settings.docDiagramsDotTimeout) + val width = (baseList map (_.helpSyntax.length)).max + def helpStr(s: doc.Settings#Setting) = ("%-" + width + "s") format (s.helpSyntax) + " " + s.helpDescription + baseList.foreach((sett: doc.Settings#Setting) => settings.printMsg(helpStr(sett))) + settings.printMsg("\nPlease note that graphviz package version 2.26 or above is required.") + settings.printMsg("**********************************************************************\n\n") + + } + } + + result + } + + def cleanup() = + if (dotProcess != null) + dotProcess.cleanup() +} + +class DotProcess(settings: doc.Settings) { + + @volatile var error: Boolean = false // signal an error + val inputString = new SyncVar[String] // used for the dot process input + val outputString = new SyncVar[String] // used for the dot process output + val errorBuffer: StringBuffer = new StringBuffer() // buffer used for both dot process error console AND logging + + // set in only one place, in the main thread + var process: Process = null + var templateName: String = "" + var templateInput: String = "" + + def feedToDot(input: String, template: String): String = { + + templateName = template + templateInput = input + + try { + + // process creation + if (process == null) { + val procIO = new ProcessIO(inputFn(_), outputFn(_), errorFn(_)) + val processBuilder: ProcessBuilder = Seq(settings.docDiagramsDotPath.value, "-Tsvg") + process = processBuilder.run(procIO) + } + + // pass the input and wait for the output + assert(!inputString.isSet) + assert(!outputString.isSet) + inputString.put(input) + var result = outputString.take(settings.docDiagramsDotTimeout.value * 1000) + if (error) result = null + + result + + } catch { + case exc => + errorBuffer.append(" Main thread in " + templateName + ": " + + (if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc)) + error = true + return null + } + } + + def cleanup(): Unit = { + + // we'll need to know if there was any error for reporting + val _error = error + + if (process != null) { + // if there's no error, this should exit cleanly + if (!error) feedToDot("", "") + + // just in case there's any thread hanging, this will take it out of the loop + error = true + process.destroy() + // we'll need to unblock the input again + if (!inputString.isSet) inputString.put("") + if (outputString.isSet) outputString.take() + } + + if (_error) { + if (settings.docDiagramsDebug.value) { + settings.printMsg("\n**********************************************************************") + settings.printMsg("The graphviz dot diagram tool has malfunctioned and will be restarted.") + settings.printMsg("\nThe following is the log of the failure:") + settings.printMsg(errorBuffer.toString) + settings.printMsg(" Cleanup: Last template: " + templateName) + settings.printMsg(" Cleanup: Last dot input: \n " + templateInput.replaceAll("\n","\n ") + "\n") + settings.printMsg(" Cleanup: Dot path: " + settings.docDiagramsDotPath.value) + if (process != null) + settings.printMsg(" Cleanup: Dot exit code: " + process.exitValue) + 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.") + } + } + } + + /* The standard input passing function */ + private[this] def inputFn(stdin: OutputStream): Unit = { + val writer = new BufferedWriter(new OutputStreamWriter(stdin)) + try { + var input = inputString.take() + + while (!error) { + if (input == "") { + // empty => signal to finish + stdin.close() + return + } else { + // send output to dot + writer.write(input + "\n\n") + writer.flush() + } + + if (!error) input = inputString.take() + } + stdin.close() + } catch { + case exc => + error = true + stdin.close() + errorBuffer.append(" Input thread in " + templateName + ": Exception: " + exc + "\n") + } + } + + private[this] def outputFn(stdOut: InputStream): Unit = { + val reader = new BufferedReader(new InputStreamReader(stdOut)) + var buffer: StringBuilder = new StringBuilder() + try { + var line = reader.readLine + while (!error && line != null) { + buffer.append(line + "\n") + // signal the last element in the svg (only for output) + if (line == "") { + outputString.put(buffer.toString) + buffer.setLength(0) + } + if (error) { stdOut.close(); return } + line = reader.readLine + } + assert(!outputString.isSet) + outputString.put(buffer.toString) + stdOut.close() + } catch { + case exc => + error = true + stdOut.close() + errorBuffer.append(" Output thread in " + templateName + ": Exception: " + exc + "\n") + } + } + + private[this] def errorFn(stdErr: InputStream): Unit = { + val reader = new BufferedReader(new InputStreamReader(stdErr)) + var buffer: StringBuilder = new StringBuilder() + try { + var line = reader.readLine + while (line != null) { + errorBuffer.append(" DOT : " + line + "\n") + error = true + line = reader.readLine + } + stdErr.close() + } catch { + case exc => + error = true + stdErr.close() + errorBuffer.append(" Error thread in " + templateName + ": Exception: " + exc + "\n") + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/class_diagram.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/class_diagram.png new file mode 100644 index 0000000000..9d7aec792b Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/class_diagram.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css new file mode 100644 index 0000000000..04d29580b7 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css @@ -0,0 +1,135 @@ +.diagram-container +{ + display: none; +} + +.diagram +{ + overflow: hidden; + padding-top:15px; +} + +.diagram svg +{ + display: block; + position: absolute; + visibility: hidden; + margin: auto; +} + +.diagram-help +{ + float:right; + display:none; +} + +.magnifying +{ + cursor: -webkit-zoom-in ! important; + cursor: -moz-zoom-in ! important; + cursor: pointer; +} + +#close-link +{ + position: absolute; + z-index: 100; + font-family: Arial, sans-serif; + font-size: 10pt; + text-decoration: underline; + color: #315479; +} + +#close:hover +{ + text-decoration: none; +} + +svg a +{ + cursor:pointer; +} + +svg text +{ + font-size: 10px; +} + +/* try to move the node text 1px in order to be vertically + centered (does not work in all browsers) */ +svg .node text +{ + transform: translate(0px,1px); + -ms-transform: translate(0px,1px); + -webkit-transform: translate(0px,1px); + -o-transform: translate(0px,1px); + -moz-transform: translate(0px,1px); +} + +/* hover effect for edges */ + +svg .edge.over text, +svg .edge.implicit-incoming.over polygon, +svg .edge.implicit-outgoing.over polygon +{ + fill: #202020; +} + +svg .edge.over path, +svg .edge.over polygon +{ + stroke: #202020; +} + +/* hover effect for nodes in class diagrams */ + +svg.class-diagram .node +{ + opacity: 0.75; +} + +svg.class-diagram .node.this +{ + opacity: 1.0; +} + +svg.class-diagram .node.over +{ + opacity: 1.0; +} + +svg .node.over polygon +{ + stroke: #202020; +} + +/* hover effect for nodes in package diagrams */ + +svg.package-diagram .node.class.over polygon, +svg.class-diagram .node.this.class.over polygon +{ + fill: #098552; + fill: #04663e; +} + +svg.package-diagram .node.trait.over polygon, +svg.class-diagram .node.this.trait.over polygon +{ + fill: #3c7b9b; + fill: #235d7b; +} + +svg.package-diagram .node.object.over polygon +{ + fill: #183377; +} + +svg.package-diagram .node.outside.over polygon +{ + fill: #d4d4d4; +} + +svg.package-diagram .node.default.over polygon +{ + fill: #d4d4d4; +} diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.js new file mode 100644 index 0000000000..478f2e38ac --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.js @@ -0,0 +1,324 @@ +/** + * JavaScript functions enhancing the SVG diagrams. + * + * @author Damien Obrist + */ + +var diagrams = {}; + +/** + * Initializes the diagrams in the main window. + */ +$(document).ready(function() +{ + // hide diagrams in browsers not supporting SVG + if(Modernizr && !Modernizr.inlinesvg) + return; + + // only execute this in the main window + if(diagrams.isPopup) + return; + + if($("#content-diagram").length) + $("#inheritance-diagram").css("padding-bottom", "20px"); + + $(".diagram-container").css("display", "block"); + + $(".diagram").each(function() { + // store inital dimensions + $(this).data("width", $("svg", $(this)).width()); + $(this).data("height", $("svg", $(this)).height()); + // store unscaled clone of SVG element + $(this).data("svg", $(this).get(0).childNodes[0].cloneNode(true)); + }); + + // make diagram visible, hide container + $(".diagram").css("display", "none"); + $(".diagram svg").css({ + "position": "static", + "visibility": "visible", + "z-index": "auto" + }); + + // enable linking to diagrams + if($(location).attr("hash") == "#inheritance-diagram") { + diagrams.toggle($("#inheritance-diagram-container"), true); + } else if($(location).attr("hash") == "#content-diagram") { + diagrams.toggle($("#content-diagram-container"), true); + } + + $(".diagram-link").click(function() { + diagrams.toggle($(this).parent()); + }); + + // register resize function + $(window).resize(diagrams.resize); + + // don't bubble event to parent div + // when clicking on a node of a resized + // diagram + $("svg a").click(function(e) { + e.stopPropagation(); + }); + + diagrams.initHighlighting(); +}); + +/** + * Initializes the diagrams in the popup. + */ +diagrams.initPopup = function(id) +{ + // copy diagram from main window + if(!jQuery.browser.msie) + $("body").append(opener.$("#" + id).data("svg")); + + // positioning + $("svg").css("position", "absolute"); + $(window).resize(function() + { + var svg_w = $("svg").css("width").replace("px", ""); + var svg_h = $("svg").css("height").replace("px", ""); + var x = $(window).width() / 2 - svg_w / 2; + if(x < 0) x = 0; + var y = $(window).height() / 2 - svg_h / 2; + if(y < 0) y = 0; + $("svg").css("left", x + "px"); + $("svg").css("top", y + "px"); + }); + $(window).resize(); + + diagrams.initHighlighting(); + $("svg a").click(function(e) { + opener.diagrams.redirectFromPopup(this.href.baseVal); + window.close(); + }); + $(document).keyup(function(e) { + if (e.keyCode == 27) window.close(); + }); +} + +/** + * Initializes highlighting for nodes and edges. + */ +diagrams.initHighlighting = function() +{ + // helper function since $.hover doesn't work in IE + + function hover(elements, fn) + { + elements.mouseover(fn); + elements.mouseout(fn); + } + + // inheritance edges + + hover($("svg .edge.inheritance"), function(evt){ + var toggleClass = evt.type == "mouseout" ? diagrams.removeClass : diagrams.addClass; + var parts = $(this).attr("id").split("_"); + toggleClass($("#" + parts[0] + "_" + parts[1])); + toggleClass($("#" + parts[0] + "_" + parts[2])); + toggleClass($(this)); + }); + + // nodes + + hover($("svg .node"), function(evt){ + var toggleClass = evt.type == "mouseout" ? diagrams.removeClass : diagrams.addClass; + toggleClass($(this)); + var parts = $(this).attr("id").split("_"); + var index = parts[1]; + $("svg#" + parts[0] + " .edge.inheritance").each(function(){ + var parts2 = $(this).attr("id").split("_"); + if(parts2[1] == index) + { + toggleClass($("#" + parts2[0] + "_" + parts2[2])); + toggleClass($(this)); + } else if(parts2[2] == index) + { + toggleClass($("#" + parts2[0] + "_" + parts2[1])); + toggleClass($(this)); + } + }); + }); + + // incoming implicits + + hover($("svg .node.implicit-incoming"), function(evt){ + var toggleClass = evt.type == "mouseout" ? diagrams.removeClass : diagrams.addClass; + toggleClass($(this)); + toggleClass($("svg .edge.implicit-incoming")); + toggleClass($("svg .node.this")); + }); + + hover($("svg .edge.implicit-incoming"), function(evt){ + var toggleClass = evt.type == "mouseout" ? diagrams.removeClass : diagrams.addClass; + toggleClass($(this)); + toggleClass($("svg .node.this")); + $("svg .node.implicit-incoming").each(function(){ + toggleClass($(this)); + }); + }); + + // implicit outgoing nodes + + hover($("svg .node.implicit-outgoing"), function(evt){ + var toggleClass = evt.type == "mouseout" ? diagrams.removeClass : diagrams.addClass; + toggleClass($(this)); + toggleClass($("svg .edge.implicit-outgoing")); + toggleClass($("svg .node.this")); + }); + + hover($("svg .edge.implicit-outgoing"), function(evt){ + var toggleClass = evt.type == "mouseout" ? diagrams.removeClass : diagrams.addClass; + toggleClass($(this)); + toggleClass($("svg .node.this")); + $("svg .node.implicit-outgoing").each(function(){ + toggleClass($(this)); + }); + }); +}; + +/** + * Resizes the diagrams according to the available width. + */ +diagrams.resize = function() +{ + // available width + var availableWidth = $("body").width() - 20; + + $(".diagram-container").each(function() { + // unregister click event on whole div + $(".diagram", this).unbind("click"); + var diagramWidth = $(".diagram", this).data("width"); + var diagramHeight = $(".diagram", this).data("height"); + + if(diagramWidth > availableWidth) + { + // resize diagram + var height = diagramHeight / diagramWidth * availableWidth; + $(".diagram svg", this).width(availableWidth); + $(".diagram svg", this).height(height); + + // register click event on whole div + $(".diagram", this).click(function() { + diagrams.popup($(this)); + }); + $(".diagram", this).addClass("magnifying"); + } + else + { + // restore full size of diagram + $(".diagram svg", this).width(diagramWidth); + $(".diagram svg", this).height(diagramHeight); + // don't show custom cursor any more + $(".diagram", this).removeClass("magnifying"); + } + }); +}; + +/** + * Shows or hides a diagram depending on its current state. + */ +diagrams.toggle = function(container, dontAnimate) +{ + // change class of link + $(".diagram-link", container).toggleClass("open"); + // get element to show / hide + var div = $(".diagram", container); + if (div.is(':visible')) + { + $(".diagram-help", container).hide(); + div.unbind("click"); + div.removeClass("magnifying"); + div.slideUp(100); + } + else + { + diagrams.resize(); + if(dontAnimate) + div.show(); + else + div.slideDown(100); + $(".diagram-help", container).show(); + } +}; + +/** + * Opens a popup containing a copy of a diagram. + */ +diagrams.windows = {}; +diagrams.popup = function(diagram) +{ + var id = diagram.attr("id"); + if(!diagrams.windows[id] || diagrams.windows[id].closed) { + var title = $(".symbol .name", $("#signature")).text(); + // cloning from parent window to popup somehow doesn't work in IE + // therefore include the SVG as a string into the HTML + var svgIE = jQuery.browser.msie ? $("
    ").append(diagram.data("svg")).html() : ""; + var html = '' + + '\n' + + '\n' + + '\n' + + ' \n' + + ' ' + title + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' Close this window\n' + + ' ' + svgIE + '\n' + + ' \n' + + ''; + + var padding = 30; + var screenHeight = screen.availHeight; + var screenWidth = screen.availWidth; + var w = Math.min(screenWidth, diagram.data("width") + 2 * padding); + var h = Math.min(screenHeight, diagram.data("height") + 2 * padding); + var left = (screenWidth - w) / 2; + var top = (screenHeight - h) / 2; + var parameters = "height=" + h + ", width=" + w + ", left=" + left + ", top=" + top + ", scrollbars=yes, location=no, resizable=yes"; + var win = window.open("about:blank", "_blank", parameters); + win.document.open(); + win.document.write(html); + win.document.close(); + diagrams.windows[id] = win; + } + win.focus(); +}; + +/** + * This method is called from within the popup when a node is clicked. + */ +diagrams.redirectFromPopup = function(url) +{ + window.location = url; +}; + +/** + * Helper method that adds a class to a SVG element. + */ +diagrams.addClass = function(svgElem, newClass) { + newClass = newClass || "over"; + var classes = svgElem.attr("class"); + if ($.inArray(newClass, classes.split(/\s+/)) == -1) { + classes += (classes ? ' ' : '') + newClass; + svgElem.attr("class", classes); + } +}; + +/** + * Helper method that removes a class from a SVG element. + */ +diagrams.removeClass = function(svgElem, oldClass) { + oldClass = oldClass || "over"; + var classes = svgElem.attr("class"); + classes = $.grep(classes.split(/\s+/), function(n, i) { return n != oldClass; }).join(' '); + svgElem.attr("class", classes); +}; + diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/modernizr.custom.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/modernizr.custom.js new file mode 100644 index 0000000000..4688d633fe --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/modernizr.custom.js @@ -0,0 +1,4 @@ +/* Modernizr 2.5.3 (Custom Build) | MIT & BSD + * Build: http://www.modernizr.com/download/#-inlinesvg + */ +;window.Modernizr=function(a,b,c){function u(a){i.cssText=a}function v(a,b){return u(prefixes.join(a+";")+(b||""))}function w(a,b){return typeof a===b}function x(a,b){return!!~(""+a).indexOf(b)}function y(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:w(f,"function")?f.bind(d||b):f}return!1}var d="2.5.3",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l={svg:"http://www.w3.org/2000/svg"},m={},n={},o={},p=[],q=p.slice,r,s={}.hasOwnProperty,t;!w(s,"undefined")&&!w(s.call,"undefined")?t=function(a,b){return s.call(a,b)}:t=function(a,b){return b in a&&w(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=q.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(q.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(q.call(arguments)))};return e}),m.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==l.svg};for(var z in m)t(m,z)&&(r=z.toLowerCase(),e[r]=m[z](),p.push((e[r]?"":"no-")+r));return u(""),h=j=null,e._version=d,e}(this,this.document); \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_diagram.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_diagram.png new file mode 100644 index 0000000000..6e9f2f743f Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_diagram.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/raphael-min.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/raphael-min.js new file mode 100644 index 0000000000..d30dbad858 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/raphael-min.js @@ -0,0 +1,10 @@ +// ┌────────────────────────────────────────────────────────────────────┐ \\ +// │ Raphaël 2.1.0 - JavaScript Vector Library │ \\ +// ├────────────────────────────────────────────────────────────────────┤ \\ +// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ +// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\ +// ├────────────────────────────────────────────────────────────────────┤ \\ +// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\ +// └────────────────────────────────────────────────────────────────────┘ \\ + +(function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;tf*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;yd)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" × "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)x(e,g)||x(b,d)x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)n)k/=2,l+=(m1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;od;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.xc.x||c.xb.x)&&(b.yc.y||c.yb.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=aF&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);ke){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael) \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index b25f0d0b3f..1bee55313b 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -163,7 +163,7 @@ text-decoration: none; background: url("arrow-right.png") no-repeat 0 3px transparent; } -.toggleContainer.open .toggle { +.toggleContainer .toggle.open { background: url("arrow-down.png") no-repeat 0 3px transparent; } @@ -171,10 +171,6 @@ text-decoration: none; margin-top: 5px; } -.toggleContainer .showElement { - padding-left: 15px; -} - .value #definition { background-color: #2C475C; /* blue */ background-image:url('defbg-blue.gif'); @@ -341,11 +337,7 @@ div.members > ol > li:last-child { font-style: italic; } -.signature .symbol .implicit.deprecated { - text-decoration: line-through; -} - -.signature .symbol .name.deprecated { +.signature .symbol .deprecated { text-decoration: line-through; } diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js index 33fbd83bee..c418c3280b 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js @@ -8,7 +8,8 @@ $(document).ready(function(){ name == 'scala.Predef.any2stringfmt' || name == 'scala.Predef.any2stringadd' || name == 'scala.Predef.any2ArrowAssoc' || - name == 'scala.Predef.any2Ensuring' + name == 'scala.Predef.any2Ensuring' || + name == 'scala.collection.TraversableOnce.alternateImplicit' }; $("#linearization li:gt(0)").filter(function(){ @@ -184,21 +185,18 @@ $(document).ready(function(){ }); /* Linear super types and known subclasses */ - function toggleShowContentFct(outerElement){ - var content = $(".hiddenContent", outerElement); - var vis = $(":visible", content); - if (vis.length > 0) { + function toggleShowContentFct(e){ + e.toggleClass("open"); + var content = $(".hiddenContent", e.parent().get(0)); + if (content.is(':visible')) { content.slideUp(100); - $(".showElement", outerElement).show(); - $(".hideElement", outerElement).hide(); } else { content.slideDown(100); - $(".showElement", outerElement).hide(); - $(".hideElement", outerElement).show(); } }; - $(".toggleContainer").click(function() { + + $(".toggle:not(.diagram-link)").click(function() { toggleShowContentFct($(this)); }); diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/trait_diagram.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/trait_diagram.png new file mode 100644 index 0000000000..88983254ce Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/trait_diagram.png differ -- cgit v1.2.3 From f8057d22235c77d69d72d6ea4d4ebdc2eeb95cdf Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 13 Jun 2012 17:19:00 +0200 Subject: Documented SyncVar Since we used it in the DocRunner and noticed it could have better documentation. Review by @heathermiller. --- src/library/scala/concurrent/SyncVar.scala | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/library/scala/concurrent/SyncVar.scala b/src/library/scala/concurrent/SyncVar.scala index 5a6d95c2ed..292014706d 100644 --- a/src/library/scala/concurrent/SyncVar.scala +++ b/src/library/scala/concurrent/SyncVar.scala @@ -53,6 +53,8 @@ class SyncVar[A] { value } + /** Waits for this SyncVar to become defined and returns + * the result */ def take(): A = synchronized { try get finally unsetVal() @@ -64,7 +66,8 @@ class SyncVar[A] { * the SyncVar. * * @param timeout the amount of milliseconds to wait, 0 means forever - * @return `None` if variable is undefined after `timeout`, `Some(value)` otherwise + * @return the value or a throws an exception if the timeout occurs + * @throws NoSuchElementException on timeout */ def take(timeout: Long): A = synchronized { try get(timeout).get @@ -72,25 +75,28 @@ class SyncVar[A] { } // TODO: this method should be private - // [Heather] the reason why: it doesn't take into consideration + // [Heather] the reason why: it doesn't take into consideration // whether or not the SyncVar is already defined. So, set has been // deprecated in order to eventually be able to make "setting" private @deprecated("Use `put` instead, as `set` is potentionally error-prone", "2.10.0") def set(x: A): Unit = setVal(x) + /** Places a value in the SyncVar. If the SyncVar already has a stored value, + * it waits until another thread takes it */ def put(x: A): Unit = synchronized { while (isDefined) wait() setVal(x) } + /** Checks whether a value is stored in the synchronized variable */ def isSet: Boolean = synchronized { isDefined } // TODO: this method should be private - // [Heather] the reason why: it doesn't take into consideration + // [Heather] the reason why: it doesn't take into consideration // whether or not the SyncVar is already defined. So, unset has been - // deprecated in order to eventually be able to make "unsetting" private + // deprecated in order to eventually be able to make "unsetting" private @deprecated("Use `take` instead, as `unset` is potentionally error-prone", "2.10.0") def unset(): Unit = synchronized { isDefined = false @@ -98,7 +104,7 @@ class SyncVar[A] { notifyAll() } - // `setVal` exists so as to retroactively deprecate `set` without + // `setVal` exists so as to retroactively deprecate `set` without // deprecation warnings where we use `set` internally. The // implementation of `set` was moved to `setVal` to achieve this private def setVal(x: A): Unit = synchronized { @@ -107,13 +113,13 @@ class SyncVar[A] { notifyAll() } - // `unsetVal` exists so as to retroactively deprecate `unset` without + // `unsetVal` exists so as to retroactively deprecate `unset` without // deprecation warnings where we use `unset` internally. The // implementation of `unset` was moved to `unsetVal` to achieve this private def unsetVal(): Unit = synchronized { isDefined = false value = None - notifyAll() + notifyAll() } } -- cgit v1.2.3 From f8cb1aee92fa19e38a1481a4e614cd866ef238c0 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 28 Jun 2012 00:03:56 +0200 Subject: Diagram tweaks #1 - relaxed the restrictions on nodes - nodes can be classes, traits and objects, both stand-alone and companion objects -- all are added to the diagram, but usually companion objects are filtered out as they don't have any superclasses - changed the rules for default diagram creation: - classes and traits (and AnyRef) get inheritance diagrams - packages and objects get content diagrams (can be overridden by @contentDiagram [hideDiagram] and @inheritanceDiagram [hideDiagram]) - tweaked the model to register subclasses of Any - hardcoded the scala package diagram to show all relations - enabled @contentDiagram showInheritedNodes by default and changed the setting to hideInheritedNodes (and added a test for this) - better node selection (can select nodes that don't have a corresponding trait) - fixed the docsite link in member selection, which was broken since the first commit :)) --- .../scala/tools/nsc/doc/html/page/Template.scala | 2 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 17 +-- .../doc/model/ModelFactoryImplicitSupport.scala | 14 +-- .../nsc/doc/model/comment/CommentFactory.scala | 6 +- .../tools/nsc/doc/model/diagram/Diagram.scala | 2 +- .../doc/model/diagram/DiagramDirectiveParser.scala | 56 +++++---- .../nsc/doc/model/diagram/DiagramFactory.scala | 131 ++++++++++++++------- src/library/scala/package.scala | 1 + test/scaladoc/run/diagrams-filtering.scala | 8 +- test/scaladoc/run/diagrams-inherited-nodes.check | 1 + test/scaladoc/run/diagrams-inherited-nodes.scala | 69 +++++++++++ 11 files changed, 223 insertions(+), 84 deletions(-) create mode 100644 test/scaladoc/run/diagrams-inherited-nodes.check create mode 100644 test/scaladoc/run/diagrams-inherited-nodes.scala diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 24cff1b475..0d0410c7e2 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -144,7 +144,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
  • Hide All
  • Show all
  • - Learn more about member selection + Learn more about member selection
    } { diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 8eb6e358b9..57625a5e15 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -45,8 +45,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { memberSym.isOmittablePrefix || (closestPackage(memberSym) == closestPackage(templateSym)) } - private lazy val noSubclassCache = Set[Symbol](AnyClass, AnyRefClass, ObjectClass) - def makeModel: Option[Universe] = { val universe = new Universe { thisUniverse => thisFactory.universe = thisUniverse @@ -270,7 +268,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def parentTypes = if (sym.isPackage || sym == AnyClass) List() else { val tps = sym.tpe.parents map { _.asSeenFrom(sym.thisType, sym) } - makeParentTypes(RefinedType(tps, EmptyScope), inTpl) + makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl) } protected def linearizationFromSymbol(symbol: Symbol): List[(TemplateEntity, TypeEntity)] = { @@ -290,7 +288,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { /* Subclass cache */ private lazy val subClassesCache = ( - if (noSubclassCache(sym)) null + if (sym == AnyRefClass) null else mutable.ListBuffer[DocTemplateEntity]() ) def registerSubClass(sc: DocTemplateEntity): Unit = { @@ -796,10 +794,15 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /** Get the types of the parents of the current class, ignoring the refinements */ - def makeParentTypes(aType: Type, inTpl: => TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { + def makeParentTypes(aType: Type, tpl: Option[DocTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { case RefinedType(parents, defs) => - val ignoreParents = Set[Symbol](AnyClass, ObjectClass) - val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) + val ignoreParents = Set[Symbol](AnyRefClass, ObjectClass) + val filtParents = + // we don't want to expose too many links to AnyRef, that will just be redundant information + if (tpl.isDefined && (!tpl.get.isObject && parents.length < 2)) + parents + else + parents.filterNot((p: Type) => ignoreParents(p.typeSymbol)) filtParents.map(parent => { val templateEntity = makeTemplate(parent.typeSymbol) val typeEntity = makeType(parent, inTpl) diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index e7f4a9c79b..8cbf2ac1b6 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -91,7 +91,7 @@ trait ModelFactoryImplicitSupport { * default Scala imports (Predef._ for example) and the companion object of the current class, if one exists. In the * future we might want to extend this to more complex scopes. */ - def makeImplicitConversions(sym: Symbol, inTpl: => DocTemplateImpl): List[ImplicitConversionImpl] = + def makeImplicitConversions(sym: Symbol, inTpl: DocTemplateImpl): List[ImplicitConversionImpl] = // Nothing and Null are somewhat special -- they can be transformed by any implicit conversion available in scope. // But we don't want that, so we'll simply refuse to find implicit conversions on for Nothing and Null if (!(sym.isClass || sym.isTrait || sym == AnyRefClass) || sym == NothingClass || sym == NullClass) Nil @@ -148,7 +148,7 @@ trait ModelFactoryImplicitSupport { * - we also need to transform implicit parameters in the view's signature into constraints, such that Numeric[T4] * appears as a constraint */ - def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: => DocTemplateImpl): List[ImplicitConversionImpl] = + def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: DocTemplateImpl): List[ImplicitConversionImpl] = if (result.tree == EmptyTree) Nil else { // `result` will contain the type of the view (= implicit conversion method) @@ -206,7 +206,7 @@ trait ModelFactoryImplicitSupport { } } - def makeImplicitConstraints(types: List[Type], sym: Symbol, context: Context, inTpl: => DocTemplateImpl): List[Constraint] = + def makeImplicitConstraints(types: List[Type], sym: Symbol, context: Context, inTpl: DocTemplateImpl): List[Constraint] = types.flatMap((tpe:Type) => { // TODO: Before creating constraints, map typeVarToOriginOrWildcard on the implicitTypes val implType = typeVarToOriginOrWildcard(tpe) @@ -282,7 +282,7 @@ trait ModelFactoryImplicitSupport { } }) - def makeSubstitutionConstraints(subst: TreeTypeSubstituter, inTpl: => DocTemplateImpl): List[Constraint] = + def makeSubstitutionConstraints(subst: TreeTypeSubstituter, inTpl: DocTemplateImpl): List[Constraint] = (subst.from zip subst.to) map { case (from, to) => new EqualTypeParamConstraint { @@ -292,7 +292,7 @@ trait ModelFactoryImplicitSupport { } } - def makeBoundedConstraints(tparams: List[Symbol], constrs: List[TypeConstraint], inTpl: => DocTemplateImpl): List[Constraint] = + def makeBoundedConstraints(tparams: List[Symbol], constrs: List[TypeConstraint], inTpl: DocTemplateImpl): List[Constraint] = (tparams zip constrs) flatMap { case (tparam, constr) => { uniteConstraints(constr) match { @@ -341,7 +341,7 @@ trait ModelFactoryImplicitSupport { val convSym: Symbol, val toType: Type, val constrs: List[Constraint], - inTpl: => DocTemplateImpl) + inTpl: DocTemplateImpl) extends ImplicitConversion { def source: DocTemplateEntity = inTpl @@ -365,7 +365,7 @@ trait ModelFactoryImplicitSupport { case _ => error("Scaladoc implicits: Could not create template for: " + toType + " of type " + toType.getClass); None } - def targetTypeComponents: List[(TemplateEntity, TypeEntity)] = makeParentTypes(toType, inTpl) + def targetTypeComponents: List[(TemplateEntity, TypeEntity)] = makeParentTypes(toType, None, inTpl) def convertorMethod: Either[MemberEntity, String] = { var convertor: MemberEntity = null diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala index a46be37d60..2099315cc6 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -30,12 +30,12 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => protected val commentCache = mutable.HashMap.empty[(global.Symbol, TemplateImpl), Comment] - def addCommentBody(sym: global.Symbol, inTpl: => TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = { + def addCommentBody(sym: global.Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = { commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos) sym } - def comment(sym: global.Symbol, inTpl: => DocTemplateImpl): Option[Comment] = { + def comment(sym: global.Symbol, inTpl: DocTemplateImpl): Option[Comment] = { val key = (sym, inTpl) if (commentCache isDefinedAt key) Some(commentCache(key)) @@ -50,7 +50,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => * cases we have to give some `inTpl` comments (parent class for example) * to the comment of the symbol. * This function manages some of those cases : Param accessor and Primary constructor */ - def defineComment(sym: global.Symbol, inTpl: => DocTemplateImpl):Option[Comment] = { + def defineComment(sym: global.Symbol, inTpl: DocTemplateImpl):Option[Comment] = { //param accessor case // We just need the @param argument, we put it into the body 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 28a8c7d37d..d80999e149 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -109,7 +109,7 @@ case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) 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 package (and @contentDiagram showInheritedNodes annotation) */ + * its relation to a class in the template (see @contentDiagram hideInheritedNodes annotation) */ case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isOutsideNode = true } diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala index beaa045df4..49cfaffc2e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala @@ -19,6 +19,8 @@ import html.page.diagram.DiagramStats trait DiagramDirectiveParser { this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory => + import this.global.definitions.AnyRefClass + ///// DIAGRAM FILTERS ////////////////////////////////////////////////////////////////////////////////////////////// /** @@ -48,16 +50,22 @@ trait DiagramDirectiveParser { /** Hide subclasses (for type hierarchy diagrams) */ def hideSubclasses: Boolean /** Show related classes from other objects/traits/packages (for content diagrams) */ - def showInheritedNodes: Boolean + def hideInheritedNodes: Boolean /** Hide a node from the diagram */ - def hideNode(clazz: TemplateEntity): Boolean + def hideNode(clazz: Node): Boolean /** Hide an edge from the diagram */ - def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean + def hideEdge(clazz1: Node, clazz2: Node): Boolean } /** Main entry point into this trait: generate the filter for inheritance diagrams */ def makeInheritanceDiagramFilter(template: DocTemplateImpl): DiagramFilter = { - val defaultFilter = if (template.isClass || template.isTrait) FullDiagram else NoDiagramAtAll + + val defaultFilter = + if (template.isClass || template.isTrait || template.sym == AnyRefClass) + FullDiagram + else + NoDiagramAtAll + if (template.comment.isDefined) makeDiagramFilter(template, template.comment.get.inheritDiagram, defaultFilter, true) else @@ -83,9 +91,9 @@ trait DiagramDirectiveParser { val hideOutgoingImplicits: Boolean = false val hideSuperclasses: Boolean = false val hideSubclasses: Boolean = false - val showInheritedNodes: Boolean = false - def hideNode(clazz: TemplateEntity): Boolean = false - def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = false + val hideInheritedNodes: Boolean = false + def hideNode(clazz: Node): Boolean = false + def hideEdge(clazz1: Node, clazz2: Node): Boolean = false } /** Hide the diagram completely, no need for special filtering */ @@ -95,9 +103,9 @@ trait DiagramDirectiveParser { val hideOutgoingImplicits: Boolean = true val hideSuperclasses: Boolean = true val hideSubclasses: Boolean = true - val showInheritedNodes: Boolean = false - def hideNode(clazz: TemplateEntity): Boolean = true - def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = true + val hideInheritedNodes: Boolean = true + def hideNode(clazz: Node): Boolean = true + def hideEdge(clazz1: Node, clazz2: Node): Boolean = true } /** The AnnotationDiagramFilter trait directs the diagram engine according to an annotation @@ -107,12 +115,18 @@ trait DiagramDirectiveParser { hideOutgoingImplicits: Boolean, hideSuperclasses: Boolean, hideSubclasses: Boolean, - showInheritedNodes: Boolean, + hideInheritedNodes: Boolean, hideNodesFilter: List[Pattern], hideEdgesFilter: List[(Pattern, Pattern)]) extends DiagramFilter { - def hideNode(clazz: TemplateEntity): Boolean = { - val qualifiedName = clazz.qualifiedName + private[this] def getName(n: Node): String = + if (n.tpl.isDefined) + n.tpl.get.qualifiedName + else + n.name + + def hideNode(clazz: Node): Boolean = { + val qualifiedName = getName(clazz) for (hideFilter <- hideNodesFilter) if (hideFilter.matcher(qualifiedName).matches) { // println(hideFilter + ".matcher(" + qualifiedName + ").matches = " + hideFilter.matcher(qualifiedName).matches) @@ -121,9 +135,9 @@ trait DiagramDirectiveParser { false } - def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = { - val clazz1Name = clazz1.qualifiedName - val clazz2Name = clazz2.qualifiedName + def hideEdge(clazz1: Node, clazz2: Node): Boolean = { + val clazz1Name = getName(clazz1) + val clazz2Name = getName(clazz2) for ((clazz1Filter, clazz2Filter) <- hideEdgesFilter) { if (clazz1Filter.matcher(clazz1Name).matches && clazz2Filter.matcher(clazz2Name).matches) { @@ -162,7 +176,7 @@ trait DiagramDirectiveParser { var hideOutgoingImplicits0: Boolean = false var hideSuperclasses0: Boolean = false var hideSubclasses0: Boolean = false - var showInheritedNodes0: Boolean = false + var hideInheritedNodes0: Boolean = false var hideNodesFilter0: List[Pattern] = Nil var hideEdgesFilter0: List[(Pattern, Pattern)] = Nil @@ -190,8 +204,8 @@ trait DiagramDirectiveParser { hideSuperclasses0 = true case "hideSubclasses" if isInheritanceDiagram => hideSubclasses0 = true - case "showInheritedNodes" if !isInheritanceDiagram => - showInheritedNodes0 = true + case "hideInheritedNodes" if !isInheritanceDiagram => + hideInheritedNodes0 = true case HideNodesRegex(last) => val matcher = NodeSpecPattern.matcher(entry) while (matcher.find()) { @@ -225,7 +239,7 @@ trait DiagramDirectiveParser { (hideOutgoingImplicits0 == false) && (hideSuperclasses0 == false) && (hideSubclasses0 == false) && - (showInheritedNodes0 == false) && + (hideInheritedNodes0 == false) && (hideDiagram0 == false)) FullDiagram else @@ -235,7 +249,7 @@ trait DiagramDirectiveParser { hideOutgoingImplicits = hideOutgoingImplicits0, hideSuperclasses = hideSuperclasses0, hideSubclasses = hideSubclasses0, - showInheritedNodes = showInheritedNodes0, + hideInheritedNodes = hideInheritedNodes0, hideNodesFilter = hideNodesFilter0, hideEdgesFilter = hideEdgesFilter0) 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 4ae5e7a5cb..3f054b969b 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -9,6 +9,8 @@ import collection.mutable // statistics import html.page.diagram.DiagramStats +import scala.collection.immutable.SortedMap + /** * This trait takes care of generating the diagram for classes and packages * @@ -18,6 +20,20 @@ import html.page.diagram.DiagramStats trait DiagramFactory extends DiagramDirectiveParser { this: ModelFactory 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, (TemplateEntity, Int)]() }, None) + /** Create the inheritance diagram for this template */ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { @@ -35,28 +51,36 @@ trait DiagramFactory extends DiagramDirectiveParser { val thisNode = ThisNode(tpl.ownType, Some(tpl)) // superclasses - var superclasses = List[Node]() - tpl.parentTypes.collect { case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => p } foreach { - t: (TemplateEntity, TypeEntity) => - val n = NormalNode(t._2, Some(t._1)) - superclasses ::= n - } - val filteredSuperclasses = if (diagramFilter.hideSuperclasses) Nil else superclasses + var superclasses: List[Node] = + tpl.parentTypes.collect { + case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => NormalNode(p._2, Some(p._1)) + }.reverse // incoming implcit conversions lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map(tpl => ImplicitNode(tpl.ownType, Some(tpl))) - val filteredIncomingImplicits = if (diagramFilter.hideIncomingImplicits) Nil else incomingImplicitNodes // subclasses - val subclasses = tpl.directSubClasses.flatMap { - case d: TemplateEntity if !classExcluded(d) => List(NormalNode(d.ownType, Some(d))) - case _ => Nil - } - val filteredSubclasses = if (diagramFilter.hideSubclasses) Nil else subclasses + var subclasses: List[Node] = + tpl.directSubClasses.flatMap { + case d: TemplateEntity if !classExcluded(d) => List(NormalNode(d.ownType, Some(d))) + case _ => Nil + }.sortBy(_.tpl.get.name)(implicitly[Ordering[String]].reverse) // outgoing implicit coversions - lazy val implicitNodes = tpl.outgoingImplicitlyConvertedClasses.map(pair => ImplicitNode(pair._2, Some(pair._1))) - val filteredImplicitOutgoingNodes = if (diagramFilter.hideOutgoingImplicits) Nil else implicitNodes + lazy val outgoingImplicitNodes = tpl.outgoingImplicitlyConvertedClasses.map(pair => ImplicitNode(pair._2, Some(pair._1))) + + // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. + // Currently, it's possible to leave nodes and edges out, but there's no way to create new nodes and edges + // The implementation would need to add the annotations and the logic to select nodes (or create new ones) + // and add edges to the diagram -- I bet it wouldn't take too long for someone to do it (one or two days + // at most) and it would be a great add to the diagrams. + if (tpl.sym == AnyRefClass) + subclasses = List(aggregationNode("All user-defined classes and traits")) + + val filteredSuperclasses = if (diagramFilter.hideSuperclasses) Nil else superclasses + val filteredIncomingImplicits = if (diagramFilter.hideIncomingImplicits) Nil else incomingImplicitNodes + val filteredSubclasses = if (diagramFilter.hideSubclasses) Nil else subclasses + val filteredImplicitOutgoingNodes = if (diagramFilter.hideOutgoingImplicits) Nil else outgoingImplicitNodes // final diagram filter filterDiagram(ClassDiagram(thisNode, filteredSuperclasses.reverse, filteredSubclasses.reverse, filteredIncomingImplicits, filteredImplicitOutgoingNodes), diagramFilter) @@ -82,39 +106,68 @@ trait DiagramFactory extends DiagramDirectiveParser { if (diagramFilter == NoDiagramAtAll) None else { - var mapNodes = Map[DocTemplateEntity, Node]() - var nodesShown = Set[DocTemplateEntity]() - var edgesAll = List[(DocTemplateEntity, List[DocTemplateEntity])]() + var mapNodes = Map[TemplateEntity, Node]() + var nodesShown = Set[TemplateEntity]() + var edgesAll = List[(TemplateEntity, List[TemplateEntity])]() // classes is the entire set of classes and traits in the package, they are the superset of nodes in the diagram // we collect classes, traits and objects without a companion, which are usually used as values(e.g. scala.None) - val dnodes = pack.members collect { - case d: DocTemplateEntity if d.isClass || d.isTrait || (d.isObject && !d.companion.isDefined) && - ((d.inTemplate == pack) || diagramFilter.showInheritedNodes) => d + val nodesAll = pack.members collect { + case d: TemplateEntity if ((!diagramFilter.hideInheritedNodes) || (d.inTemplate == pack)) => d } // for each node, add its subclasses - for (node <- dnodes if !classExcluded(node)) { - val superClasses = node.parentTypes.collect { - case (tpl: DocTemplateEntity, tpe) if tpl.inTemplate == pack && !classExcluded(tpl) => tpl - case (tpl: DocTemplateEntity, tpe) if tpl.inTemplate != pack && !classExcluded(tpl) && diagramFilter.showInheritedNodes && (pack.members contains tpl) => tpl - } - - if (!superClasses.isEmpty) { - nodesShown += node - nodesShown ++= superClasses + for (node <- nodesAll if !classExcluded(node)) { + node match { + case dnode: DocTemplateImpl => + var superClasses = dnode.parentTypes.map(_._1) + + superClasses = superClasses.filter(nodesAll.contains(_)) + + // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. + if (pack.sym == ScalaPackage) + if (dnode.sym == NullClass) + superClasses = List(makeTemplate(AnyRefClass)) + else if (dnode.sym == NothingClass) + superClasses = (List(NullClass) ::: ScalaValueClasses).map(makeTemplate(_)) + + if (!superClasses.isEmpty) { + nodesShown += dnode + nodesShown ++= superClasses + } + edgesAll ::= dnode -> superClasses + case _ => } - edgesAll ::= node -> superClasses mapNodes += node -> (if (node.inTemplate == pack) NormalNode(node.ownType, Some(node)) else OutsideNode(node.ownType, Some(node))) } if (nodesShown.isEmpty) None else { - val nodes = dnodes.filter(nodesShown.contains(_)).map(mapNodes(_)) + val nodes = nodesAll.filter(nodesShown.contains(_)).map(mapNodes(_)) val edges = edgesAll.map(pair => (mapNodes(pair._1), pair._2.map(mapNodes(_)))).filterNot(pair => pair._2.isEmpty) - filterDiagram(PackageDiagram(nodes, edges), diagramFilter) + val diagram = + // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. + if (pack.sym == ScalaPackage) { + // Tried it, but it doesn't look good: + // var anyRefSubtypes: List[Node] = List(mapNodes(makeTemplate(AnyRefClass))) + // var dirty = true + // do { + // val length = anyRefSubtypes.length + // anyRefSubtypes :::= edges.collect { case p: (Node, List[Node]) if p._2.exists(anyRefSubtypes.contains(_)) => p._1 } + // anyRefSubtypes = anyRefSubtypes.distinct + // dirty = (anyRefSubtypes.length != length) + // } while (dirty) + // println(anyRefSubtypes) + val anyRefSubtypes = Nil + val allAnyRefTypes = aggregationNode("All AnyRef subtypes") + val nullTemplate = makeTemplate(NullClass) + PackageDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) + } else + PackageDiagram(nodes, edges) + + filterDiagram(diagram, diagramFilter) } } @@ -137,18 +190,16 @@ trait DiagramFactory extends DiagramDirectiveParser { else { // Final diagram, with the filtered nodes and edges diagram match { - case ClassDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode.tpl.get) => + case ClassDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode) => None case ClassDiagram(thisNode, superClasses, subClasses, incomingImplicits, outgoingImplicits) => def hideIncoming(node: Node): Boolean = - if (node.tpl.isDefined) diagramFilter.hideNode(node.tpl.get) || diagramFilter.hideEdge(node.tpl.get, thisNode.tpl.get) - else false // hopefully we won't need to fallback here + diagramFilter.hideNode(node) || diagramFilter.hideEdge(node, thisNode) def hideOutgoing(node: Node): Boolean = - if (node.tpl.isDefined) diagramFilter.hideNode(node.tpl.get) || diagramFilter.hideEdge(thisNode.tpl.get, node.tpl.get) - else false // hopefully we won't need to fallback here + diagramFilter.hideNode(node) || diagramFilter.hideEdge(thisNode, node) // println(thisNode) // println(superClasses.map(cl => "super: " + cl + " " + hideOutgoing(cl)).mkString("\n")) @@ -166,8 +217,8 @@ trait DiagramFactory extends DiagramDirectiveParser { // (3) are destinations of hidden classes val edges: List[(Node, List[Node])] = diagram.edges.flatMap({ - case (source@Node(_, Some(tpl1)), dests) if !diagramFilter.hideNode(tpl1) => - val dests2 = dests.collect({ case node@Node(_, Some(tpl2)) if (!(diagramFilter.hideEdge(tpl1, tpl2) || diagramFilter.hideNode(tpl2))) => node }) + case (source, dests) if !diagramFilter.hideNode(source) => + val dests2 = dests.collect({ case dest if (!(diagramFilter.hideEdge(source, dest) || diagramFilter.hideNode(dest))) => dest }) if (dests2 != Nil) List((source, dests2)) else diff --git a/src/library/scala/package.scala b/src/library/scala/package.scala index e3890d7a9d..6460db534d 100644 --- a/src/library/scala/package.scala +++ b/src/library/scala/package.scala @@ -9,6 +9,7 @@ /** * Core Scala types. They are always available without an explicit import. + * @contentDiagram hideNodes "scala.Serializable" */ package object scala { type Throwable = java.lang.Throwable diff --git a/test/scaladoc/run/diagrams-filtering.scala b/test/scaladoc/run/diagrams-filtering.scala index dfde5cac52..8cb32180a1 100644 --- a/test/scaladoc/run/diagrams-filtering.scala +++ b/test/scaladoc/run/diagrams-filtering.scala @@ -54,11 +54,11 @@ object Test extends ScaladocModelTest { assert(packDiag.edges.map(_._2.length).sum == 5) // trait A - // Assert we have just 2 nodes and 1 edge + // Assert we have just 3 nodes and 2 edges val A = base._trait("A") val ADiag = A.inheritanceDiagram.get - assert(ADiag.nodes.length == 2) - assert(ADiag.edges.map(_._2.length).sum == 1) + assert(ADiag.nodes.length == 3) + assert(ADiag.edges.map(_._2.length).sum == 2) // trait C val C = base._trait("C") @@ -82,7 +82,7 @@ object Test extends ScaladocModelTest { val (outgoingSuperclass, outgoingImplicit) = outgoing.head._2.partition(_.isNormalNode) assert(outgoingSuperclass.length == 2) // B and C - assert(outgoingImplicit.length == 1) // T + assert(outgoingImplicit.length == 1, outgoingImplicit) // T val (incomingSubclass, incomingImplicit) = incoming.partition(_._1.isNormalNode) assert(incomingSubclass.length == 2) // F and G diff --git a/test/scaladoc/run/diagrams-inherited-nodes.check b/test/scaladoc/run/diagrams-inherited-nodes.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/diagrams-inherited-nodes.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/diagrams-inherited-nodes.scala b/test/scaladoc/run/diagrams-inherited-nodes.scala new file mode 100644 index 0000000000..8ac382aab8 --- /dev/null +++ b/test/scaladoc/run/diagrams-inherited-nodes.scala @@ -0,0 +1,69 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.diagrams.inherited.nodes { + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + trait T1 { + trait A1 + trait A2 extends A1 + trait A3 extends A2 + } + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + trait T2 extends T1 { + trait B1 extends A1 + trait B2 extends A2 with B1 + trait B3 extends A3 with B2 + } + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + trait T3 { + self: T1 with T2 => + trait C1 extends B1 + trait C2 extends B2 with C1 + trait C3 extends B3 with C2 + } + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + trait T4 extends T3 with T2 with T1 { + trait D1 extends C1 + trait D2 extends C2 with D1 + trait D3 extends C3 with D2 + } + } + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "-diagrams" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // base package + // Assert we have 7 nodes and 6 edges + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("diagrams")._package("inherited")._package("nodes") + + def checkDiagram(t: String, nodes: Int, edges: Int) = { + // trait T1 + val T = base._trait(t) + val TDiag = T.contentDiagram.get + assert(TDiag.nodes.length == nodes, t + ": " + TDiag.nodes + ".length == " + nodes) + assert(TDiag.edges.map(_._2.length).sum == edges, t + ": " + TDiag.edges.mkString("List(\n", ",\n", "\n)") + ".map(_._2.length).sum == " + edges) + } + + checkDiagram("T1", 3, 2) + checkDiagram("T2", 6, 7) + checkDiagram("T3", 3, 2) + checkDiagram("T4", 12, 17) + } +} \ No newline at end of file -- cgit v1.2.3 From c410b57d55ee10b565356b8595744a804fd2fa2f Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 28 Jun 2012 04:30:30 +0200 Subject: Diagram tweaks #2 - fixed the AnyRef linking (SI-5780) - added tooltips to implicit conversions in diagrams - fixed the intermittent dot error where node images would be left out (dot is not reliable at all -- with all the mechanisms in place to fail gracefully, we still get dot errors crawling their way into diagrams - and that usually means no diagram generated, which is the most appropriate way to fail, I think...) --- .../nsc/doc/html/page/diagram/DiagramStats.scala | 8 ++ .../html/page/diagram/DotDiagramGenerator.scala | 93 ++++++++++----- .../scala/tools/nsc/doc/model/Entity.scala | 4 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 37 +++--- .../tools/nsc/doc/model/diagram/Diagram.scala | 9 +- .../nsc/doc/model/diagram/DiagramFactory.scala | 16 ++- test/scaladoc/resources/doc-root/Any.scala | 114 ++++++++++++++++++ test/scaladoc/resources/doc-root/AnyRef.scala | 131 +++++++++++++++++++++ test/scaladoc/resources/doc-root/Nothing.scala | 23 ++++ test/scaladoc/resources/doc-root/Null.scala | 17 +++ test/scaladoc/run/SI-5780.check | 1 + test/scaladoc/run/SI-5780.scala | 25 ++++ 12 files changed, 419 insertions(+), 59 deletions(-) create mode 100644 test/scaladoc/resources/doc-root/Any.scala create mode 100644 test/scaladoc/resources/doc-root/AnyRef.scala create mode 100644 test/scaladoc/resources/doc-root/Nothing.scala create mode 100644 test/scaladoc/resources/doc-root/Null.scala create mode 100644 test/scaladoc/run/SI-5780.check create mode 100644 test/scaladoc/run/SI-5780.scala diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala index 16d859894f..ec00cace75 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala @@ -38,6 +38,8 @@ object DiagramStats { private[this] val dotGenTrack = new TimeTracker("dot diagram generation") private[this] val dotRunTrack = new TimeTracker("dot process runnning") private[this] val svgTrack = new TimeTracker("svg processing") + private[this] var brokenImages = 0 + private[this] var fixedImages = 0 def printStats(settings: Settings) = { if (settings.docDiagramsDebug.value) { @@ -47,6 +49,9 @@ object DiagramStats { dotGenTrack.printStats(settings.printMsg) dotRunTrack.printStats(settings.printMsg) svgTrack.printStats(settings.printMsg) + println(" Broken images: " + brokenImages) + println(" Fixed images: " + fixedImages) + println("") } } @@ -55,4 +60,7 @@ object DiagramStats { def addDotGenerationTime(ms: Long) = dotGenTrack.addTime(ms) def addDotRunningTime(ms: Long) = dotRunTrack.addTime(ms) def addSvgTime(ms: Long) = svgTrack.addTime(ms) + + def addBrokenImage(): Unit = brokenImages += 1 + def addFixedImage(): Unit = fixedImages += 1 } \ No newline at end of file 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 f5a1d1b088..dc6f941c30 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 @@ -66,10 +66,6 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { var superClasses = List[Node]() var incomingImplicits = List[Node]() var outgoingImplicits = List[Node]() - var subClassesTooltip: Option[String] = None - var superClassesTooltip: Option[String] = None - var incomingImplicitsTooltip: Option[String] = None - var outgoingImplicitsTooltip: Option[String] = None isClassDiagram = false d match { @@ -89,23 +85,23 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { // if there are too many super / sub / implicit nodes, represent // them by on node with a corresponding tooltip superClasses = if (_superClasses.length > settings.docDiagramsMaxNormalClasses.value) { - superClassesTooltip = Some(limitSize(_superClasses.map(_.tpe.name).mkString(", "))) - List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None)) + val superClassesTooltip = Some(limitSize(_superClasses.map(_.tpe.name).mkString(", "))) + List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None, superClassesTooltip)) } else _superClasses subClasses = if (_subClasses.length > settings.docDiagramsMaxNormalClasses.value) { - subClassesTooltip = Some(limitSize(_subClasses.map(_.tpe.name).mkString(", "))) - List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None)) + val subClassesTooltip = Some(limitSize(_subClasses.map(_.tpe.name).mkString(", "))) + List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None, subClassesTooltip)) } else _subClasses incomingImplicits = if (_incomingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { - incomingImplicitsTooltip = Some(limitSize(_incomingImplicits.map(_.tpe.name).mkString(", "))) - List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None)) + val incomingImplicitsTooltip = Some(limitSize(_incomingImplicits.map(_.tpe.name).mkString(", "))) + List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None, incomingImplicitsTooltip)) } else _incomingImplicits outgoingImplicits = if (_outgoingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { - outgoingImplicitsTooltip = Some(limitSize(_outgoingImplicits.map(_.tpe.name).mkString(", "))) - List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None)) + val outgoingImplicitsTooltip = Some(limitSize(_outgoingImplicits.map(_.tpe.name).mkString(", "))) + List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None, outgoingImplicitsTooltip)) } else _outgoingImplicits thisNode = _thisNode @@ -128,14 +124,14 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { // dot cluster containing thisNode val thisCluster = "subgraph clusterThis {\n" + "style=\"invis\"\n" + - node2Dot(thisNode, None) + + node2Dot(thisNode) + "}" // dot cluster containing incoming implicit nodes, if any val incomingCluster = { if(incomingImplicits.isEmpty) "" else "subgraph clusterIncoming {\n" + "style=\"invis\"\n" + - incomingImplicits.reverse.map(n => node2Dot(n, incomingImplicitsTooltip)).mkString + + incomingImplicits.reverse.map(n => node2Dot(n)).mkString + (if (incomingImplicits.size > 1) incomingImplicits.map(n => "node" + node2Index(n)).mkString(" -> ") + " [constraint=\"false\", style=\"invis\", minlen=\"0.0\"];\n" @@ -147,7 +143,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { if(outgoingImplicits.isEmpty) "" else "subgraph clusterOutgoing {\n" + "style=\"invis\"\n" + - outgoingImplicits.reverse.map(n => node2Dot(n, outgoingImplicitsTooltip)).mkString + + outgoingImplicits.reverse.map(n => node2Dot(n)).mkString + (if (outgoingImplicits.size > 1) outgoingImplicits.map(n => "node" + node2Index(n)).mkString(" -> ") + " [constraint=\"false\", style=\"invis\", minlen=\"0.0\"];\n" @@ -189,9 +185,9 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { "edge [" + edgeAttributesStr + "];\n" + implicitsDot + "\n" + // inheritance nodes - nodes.map(n => node2Dot(n, None)).mkString + - subClasses.map(n => node2Dot(n, subClassesTooltip)).mkString + - superClasses.map(n => node2Dot(n, superClassesTooltip)).mkString + + nodes.map(n => node2Dot(n)).mkString + + subClasses.map(n => node2Dot(n)).mkString + + superClasses.map(n => node2Dot(n)).mkString + // inheritance edges edges.map{ case (from, tos) => tos.map(to => { val id = "graph" + counter + "_" + node2Index(to) + "_" + node2Index(from) @@ -213,7 +209,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { /** * Generates the dot string of a given node. */ - private def node2Dot(node: Node, tooltip: Option[String]) = { + private def node2Dot(node: Node) = { // escape HTML characters in node names def escape(name: String) = name.replace("&", "&").replace("<", "<").replace(">", ">"); @@ -228,7 +224,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { } // tooltip - tooltip match { + node.tooltip match { case Some(text) => attr += "tooltip" -> text // show full name where available (instead of TraversableOps[A] show scala.collection.parallel.TraversableOps[A]) case None if node.tpl.isDefined => attr += "tooltip" -> node.tpl.get.qualifiedName @@ -294,25 +290,23 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { */ private def cssClass(node: Node): String = if (node.isImplicitNode && incomingImplicitNodes.contains(node)) - "implicit-incoming" + "implicit-incoming" + cssBaseClass(node, "", " ") else if (node.isImplicitNode) - "implicit-outgoing" - else if (node.isObjectNode) - "object" + "implicit-outgoing" + cssBaseClass(node, "", " ") else if (node.isThisNode) - "this" + cssBaseClass(node, "") + "this" + cssBaseClass(node, "", " ") else if (node.isOutsideNode) - "outside" + cssBaseClass(node, "") + "outside" + cssBaseClass(node, "", " ") else - cssBaseClass(node, "default") + cssBaseClass(node, "default", "") - private def cssBaseClass(node: Node, default: String) = + private def cssBaseClass(node: Node, default: String, space: String) = if (node.isClassNode) - " class" + space + "class" else if (node.isTraitNode) - " trait" + space + "trait" else if (node.isObjectNode) - " trait" + space + "object" else default @@ -381,10 +375,31 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { // assign id and class attributes to edges and nodes: // the id attribute generated by dot has the format: "{class}|{id}" case g @ Elem(prefix, "g", attribs, scope, children @ _*) if (List("edge", "node").contains((g \ "@class").toString)) => { - val res = new Elem(prefix, "g", attribs, scope, (children map(x => transform(x))): _*) + var res = new Elem(prefix, "g", attribs, scope, (children map(x => transform(x))): _*) val dotId = (g \ "@id").toString if (dotId.count(_ == '|') == 1) { val Array(klass, id) = dotId.toString.split("\\|") + /* Sometimes dot "forgets" to add the image -- that's very annoying, but it seems pretty random, and simple + * tests like excute 20K times and diff the output don't trigger the bug -- so it's up to us to place the image + * back in the node */ + val kind = getKind(klass) + if (kind != "") + if (((g \ "a" \ "image").isEmpty)) { + DiagramStats.addBrokenImage() + val xposition = getPosition(g, "x", -22) + val yposition = getPosition(g, "y", -11.3334) + if (xposition.isDefined && yposition.isDefined) { + val imageNode = + val anchorNode = (g \ "a") match { + case Seq(Elem(prefix, "a", attribs, scope, children @ _*)) => + transform(new Elem(prefix, "a", attribs, scope, (children ++ imageNode): _*)) + case _ => + g \ "a" + } + res = new Elem(prefix, "g", attribs, scope, anchorNode: _*) + DiagramStats.addFixedImage() + } + } res % new UnprefixedAttribute("id", id, Null) % new UnprefixedAttribute("class", (g \ "@class").toString + " " + klass, Null) } @@ -399,6 +414,20 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { case x => x } + def getKind(klass: String): String = + if (klass.contains("class")) "class" + else if (klass.contains("trait")) "trait" + else if (klass.contains("object")) "object" + else "" + + def getPosition(g: xml.Node, axis: String, offset: Double): Option[Double] = { + val node = g \ "a" \ "text" \ ("@" + axis) + if (node.isEmpty) + None + else + Some(node.toString.toDouble + offset) + } + /* graph / node / edge attributes */ private val graphAttributes: Map[String, String] = Map( diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index e1bbf28dc7..2901daafd6 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -278,11 +278,11 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { def implicitsShadowing: Map[MemberEntity, ImplicitMemberShadowing] /** Classes that can be implcitly converted to this class */ - def incomingImplicitlyConvertedClasses: List[DocTemplateEntity] + def incomingImplicitlyConvertedClasses: List[(DocTemplateEntity, ImplicitConversion)] /** Classes to which this class can be implicitly converted to NOTE: Some classes might not be included in the scaladoc run so they will be NoDocTemplateEntities */ - def outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity)] + def outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity, ImplicitConversion)] /** If this template takes place in inheritance and implicit conversion relations, it will be shown in this diagram */ def inheritanceDiagram: Option[Diagram] diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 57625a5e15..9fa6619e9f 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -299,14 +299,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] = null - def registerImplicitlyConvertibleClass(sc: DocTemplateEntity): Unit = { + private var implicitlyConvertibleClassesCache: mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)] = null + def registerImplicitlyConvertibleClass(dtpl: DocTemplateEntity, conv: ImplicitConversionImpl): Unit = { if (implicitlyConvertibleClassesCache == null) - implicitlyConvertibleClassesCache = mutable.ListBuffer[DocTemplateEntity]() - implicitlyConvertibleClassesCache += sc + implicitlyConvertibleClassesCache = mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)]() + implicitlyConvertibleClassesCache += ((dtpl, conv)) } - def incomingImplicitlyConvertedClasses: List[DocTemplateEntity] = + def incomingImplicitlyConvertedClasses: List[(DocTemplateEntity, ImplicitConversionImpl)] = if (implicitlyConvertibleClassesCache == null) List() else @@ -363,18 +363,19 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { var implicitsShadowing = Map[MemberEntity, ImplicitMemberShadowing]() - lazy val outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity)] = conversions flatMap (conv => - if (!implicitExcluded(conv.conversionQualifiedName)) - conv.targetTypeComponents map { - case pair@(template, tpe) => - template match { - case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this) - case _ => // nothing - } - pair - } - else List() - ) + lazy val outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity, ImplicitConversionImpl)] = + conversions flatMap (conv => + if (!implicitExcluded(conv.conversionQualifiedName)) + conv.targetTypeComponents map { + case pair@(template, tpe) => + template match { + case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this, conv) + case _ => // nothing + } + (pair._1, pair._2, conv) + } + else List() + ) override def isTemplate = true lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) @@ -862,7 +863,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // nameBuffer append stripPrefixes.foldLeft(pre.prefixString)(_ stripPrefix _) // } val bSym = normalizeTemplate(aSym) - if (bSym.isNonClassType) { + if (bSym.isNonClassType && bSym != AnyRefClass) { nameBuffer append bSym.decodedName } else { val tpl = makeTemplate(bSym) 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 d80999e149..8527ca4039 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -73,6 +73,7 @@ abstract class Node { def isOtherNode = !(isClassNode || isTraitNode || isObjectNode) def isImplicitNode = false def isOutsideNode = false + def tooltip: Option[String] } // different matchers, allowing you to use the pattern matcher against any node @@ -97,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]) extends Node { override def isThisNode = true } +case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isThisNode = true } /** The usual node */ -case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isNormalNode = true } +case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity], 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]) extends Node { override def isImplicitNode = true } +case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity], 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]) extends Node { override def isOutsideNode = true } +case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity], 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 3f054b969b..1a8ad193aa 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -43,12 +43,16 @@ trait DiagramFactory extends DiagramDirectiveParser { // the diagram filter val diagramFilter = makeInheritanceDiagramFilter(tpl) + def implicitTooltip(from: DocTemplateEntity, to: TemplateEntity, conv: ImplicitConversion) = + Some(from.qualifiedName + " can be implicitly converted to " + conv.targetType + " by the implicit method " + + conv.conversionShortName + " in " + conv.convertorOwner.kind + " " + conv.convertorOwner.qualifiedName) + val result = if (diagramFilter == NoDiagramAtAll) None else { // the main node - val thisNode = ThisNode(tpl.ownType, Some(tpl)) + val thisNode = ThisNode(tpl.ownType, Some(tpl), Some(tpl.qualifiedName + " (this " + tpl.kind + ")")) // superclasses var superclasses: List[Node] = @@ -57,7 +61,10 @@ trait DiagramFactory extends DiagramDirectiveParser { }.reverse // incoming implcit conversions - lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map(tpl => ImplicitNode(tpl.ownType, Some(tpl))) + lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map { + case (incomingTpl, conv) => + ImplicitNode(incomingTpl.ownType, Some(incomingTpl), implicitTooltip(from=incomingTpl, to=tpl, conv=conv)) + } // subclasses var subclasses: List[Node] = @@ -67,7 +74,10 @@ trait DiagramFactory extends DiagramDirectiveParser { }.sortBy(_.tpl.get.name)(implicitly[Ordering[String]].reverse) // outgoing implicit coversions - lazy val outgoingImplicitNodes = tpl.outgoingImplicitlyConvertedClasses.map(pair => ImplicitNode(pair._2, Some(pair._1))) + lazy val outgoingImplicitNodes = tpl.outgoingImplicitlyConvertedClasses.map { + case (outgoingTpl, outgoingType, 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. // Currently, it's possible to leave nodes and edges out, but there's no way to create new nodes and edges diff --git a/test/scaladoc/resources/doc-root/Any.scala b/test/scaladoc/resources/doc-root/Any.scala new file mode 100644 index 0000000000..031b7d9d8c --- /dev/null +++ b/test/scaladoc/resources/doc-root/Any.scala @@ -0,0 +1,114 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +/** Class `Any` is the root of the Scala class hierarchy. Every class in a Scala + * execution environment inherits directly or indirectly from this class. + */ +abstract class Any { + /** Compares the receiver object (`this`) with the argument object (`that`) for equivalence. + * + * Any implementation of this method should be an [[http://en.wikipedia.org/wiki/Equivalence_relation equivalence relation]]: + * + * - It is reflexive: for any instance `x` of type `Any`, `x.equals(x)` should return `true`. + * - It is symmetric: for any instances `x` and `y` of type `Any`, `x.equals(y)` should return `true` if and + * only if `y.equals(x)` returns `true`. + * - It is transitive: for any instances `x`, `y`, and `z` of type `AnyRef` if `x.equals(y)` returns `true` and + * `y.equals(z)` returns `true`, then `x.equals(z)` should return `true`. + * + * If you override this method, you should verify that your implementation remains an equivalence relation. + * Additionally, when overriding this method it is usually necessary to override `hashCode` to ensure that + * objects which are "equal" (`o1.equals(o2)` returns `true`) hash to the same [[scala.Int]]. + * (`o1.hashCode.equals(o2.hashCode)`). + * + * @param that the object to compare against this object for equality. + * @return `true` if the receiver object is equivalent to the argument; `false` otherwise. + */ + def equals(that: Any): Boolean + + /** Calculate a hash code value for the object. + * + * The default hashing algorithm is platform dependent. + * + * Note that it is allowed for two objects to have identical hash codes (`o1.hashCode.equals(o2.hashCode)`) yet + * not be equal (`o1.equals(o2)` returns `false`). A degenerate implementation could always return `0`. + * However, it is required that if two objects are equal (`o1.equals(o2)` returns `true`) that they have + * identical hash codes (`o1.hashCode.equals(o2.hashCode)`). Therefore, when overriding this method, be sure + * to verify that the behavior is consistent with the `equals` method. + * + * @return the hash code value for this object. + */ + def hashCode(): Int + + /** Returns a string representation of the object. + * + * The default representation is platform dependent. + * + * @return a string representation of the object. + */ + def toString(): String + + /** Returns the runtime class representation of the object. + * + * @return a class object corresponding to the runtime type of the receiver. + */ + def getClass(): Class[_] + + /** Test two objects for equality. + * The expression `x == that` is equivalent to `if (x eq null) that eq null else x.equals(that)`. + * + * @param that the object to compare against this object for equality. + * @return `true` if the receiver object is equivalent to the argument; `false` otherwise. + */ + final def ==(that: Any): Boolean = this equals that + + /** Test two objects for inequality. + * + * @param that the object to compare against this object for equality. + * @return `true` if !(this == that), false otherwise. + */ + final def != (that: Any): Boolean = !(this == that) + + /** Equivalent to `x.hashCode` except for boxed numeric types and `null`. + * For numerics, it returns a hash value which is consistent + * with value equality: if two value type instances compare + * as true, then ## will produce the same hash value for each + * of them. + * For `null` returns a hashcode where `null.hashCode` throws a + * `NullPointerException`. + * + * @return a hash value consistent with == + */ + final def ##(): Int = sys.error("##") + + /** Test whether the dynamic type of the receiver object is `T0`. + * + * Note that the result of the test is modulo Scala's erasure semantics. + * Therefore the expression `1.isInstanceOf[String]` will return `false`, while the + * expression `List(1).isInstanceOf[List[String]]` will return `true`. + * In the latter example, because the type argument is erased as part of compilation it is + * not possible to check whether the contents of the list are of the specified type. + * + * @return `true` if the receiver object is an instance of erasure of type `T0`; `false` otherwise. + */ + def isInstanceOf[T0]: Boolean = sys.error("isInstanceOf") + + /** Cast the receiver object to be of type `T0`. + * + * Note that the success of a cast at runtime is modulo Scala's erasure semantics. + * Therefore the expression `1.asInstanceOf[String]` will throw a `ClassCastException` at + * runtime, while the expression `List(1).asInstanceOf[List[String]]` will not. + * In the latter example, because the type argument is erased as part of compilation it is + * not possible to check whether the contents of the list are of the requested type. + * + * @throws ClassCastException if the receiver object is not an instance of the erasure of type `T0`. + * @return the receiver object. + */ + def asInstanceOf[T0]: T0 = sys.error("asInstanceOf") +} diff --git a/test/scaladoc/resources/doc-root/AnyRef.scala b/test/scaladoc/resources/doc-root/AnyRef.scala new file mode 100644 index 0000000000..1eefb0c806 --- /dev/null +++ b/test/scaladoc/resources/doc-root/AnyRef.scala @@ -0,0 +1,131 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +/** Class `AnyRef` is the root class of all ''reference types''. + * All types except the value types descend from this class. + */ +trait AnyRef extends Any { + + /** The equality method for reference types. Default implementation delegates to `eq`. + * + * See also `equals` in [[scala.Any]]. + * + * @param that the object to compare against this object for equality. + * @return `true` if the receiver object is equivalent to the argument; `false` otherwise. + */ + def equals(that: Any): Boolean = this eq that + + /** The hashCode method for reference types. See hashCode in [[scala.Any]]. + * + * @return the hash code value for this object. + */ + def hashCode: Int = sys.error("hashCode") + + /** Creates a String representation of this object. The default + * representation is platform dependent. On the java platform it + * is the concatenation of the class name, "@", and the object's + * hashcode in hexadecimal. + * + * @return a String representation of the object. + */ + def toString: String = sys.error("toString") + + /** Executes the code in `body` with an exclusive lock on `this`. + * + * @param body the code to execute + * @return the result of `body` + */ + def synchronized[T](body: => T): T + + /** Tests whether the argument (`arg0`) is a reference to the receiver object (`this`). + * + * The `eq` method implements an [[http://en.wikipedia.org/wiki/Equivalence_relation equivalence relation]] on + * non-null instances of `AnyRef`, and has three additional properties: + * + * - It is consistent: for any non-null instances `x` and `y` of type `AnyRef`, multiple invocations of + * `x.eq(y)` consistently returns `true` or consistently returns `false`. + * - For any non-null instance `x` of type `AnyRef`, `x.eq(null)` and `null.eq(x)` returns `false`. + * - `null.eq(null)` returns `true`. + * + * When overriding the `equals` or `hashCode` methods, it is important to ensure that their behavior is + * consistent with reference equality. Therefore, if two objects are references to each other (`o1 eq o2`), they + * should be equal to each other (`o1 == o2`) and they should hash to the same value (`o1.hashCode == o2.hashCode`). + * + * @param that the object to compare against this object for reference equality. + * @return `true` if the argument is a reference to the receiver object; `false` otherwise. + */ + final def eq(that: AnyRef): Boolean = sys.error("eq") + + /** Equivalent to `!(this eq that)`. + * + * @param that the object to compare against this object for reference equality. + * @return `true` if the argument is not a reference to the receiver object; `false` otherwise. + */ + final def ne(that: AnyRef): Boolean = !(this eq that) + + /** The expression `x == that` is equivalent to `if (x eq null) that eq null else x.equals(that)`. + * + * @param arg0 the object to compare against this object for equality. + * @return `true` if the receiver object is equivalent to the argument; `false` otherwise. + */ + final def ==(that: AnyRef): Boolean = + if (this eq null) that eq null + else this equals that + + /** Create a copy of the receiver object. + * + * The default implementation of the `clone` method is platform dependent. + * + * @note not specified by SLS as a member of AnyRef + * @return a copy of the receiver object. + */ + protected def clone(): AnyRef + + /** Called by the garbage collector on the receiver object when there + * are no more references to the object. + * + * The details of when and if the `finalize` method is invoked, as + * well as the interaction between `finalize` and non-local returns + * and exceptions, are all platform dependent. + * + * @note not specified by SLS as a member of AnyRef + */ + protected def finalize(): Unit + + /** A representation that corresponds to the dynamic class of the receiver object. + * + * The nature of the representation is platform dependent. + * + * @note not specified by SLS as a member of AnyRef + * @return a representation that corresponds to the dynamic class of the receiver object. + */ + def getClass(): Class[_] + + /** Wakes up a single thread that is waiting on the receiver object's monitor. + * + * @note not specified by SLS as a member of AnyRef + */ + def notify(): Unit + + /** Wakes up all threads that are waiting on the receiver object's monitor. + * + * @note not specified by SLS as a member of AnyRef + */ + def notifyAll(): Unit + + /** Causes the current Thread to wait until another Thread invokes + * the notify() or notifyAll() methods. + * + * @note not specified by SLS as a member of AnyRef + */ + def wait (): Unit + def wait (timeout: Long, nanos: Int): Unit + def wait (timeout: Long): Unit +} diff --git a/test/scaladoc/resources/doc-root/Nothing.scala b/test/scaladoc/resources/doc-root/Nothing.scala new file mode 100644 index 0000000000..eed6066039 --- /dev/null +++ b/test/scaladoc/resources/doc-root/Nothing.scala @@ -0,0 +1,23 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +/** `Nothing` is - together with [[scala.Null]] - at the bottom of Scala's type hierarchy. + * + * `Nothing` is a subtype of every other type (including [[scala.Null]]); there exist + * ''no instances'' of this type. Although type `Nothing` is uninhabited, it is + * nevertheless useful in several ways. For instance, the Scala library defines a value + * [[scala.collection.immutable.Nil]] of type `List[Nothing]`. Because lists are covariant in Scala, + * this makes [[scala.collection.immutable.Nil]] an instance of `List[T]`, for any element of type `T`. + * + * Another usage for Nothing is the return type for methods which never return normally. + * One example is method error in [[scala.sys]], which always throws an exception. + */ +sealed trait Nothing + diff --git a/test/scaladoc/resources/doc-root/Null.scala b/test/scaladoc/resources/doc-root/Null.scala new file mode 100644 index 0000000000..7455e78ae7 --- /dev/null +++ b/test/scaladoc/resources/doc-root/Null.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +/** `Null` is - together with [[scala.Nothing]] - at the bottom of the Scala type hierarchy. + * + * `Null` is a subtype of all reference types; its only instance is the `null` reference. + * Since `Null` is not a subtype of value types, `null` is not a member of any such type. For instance, + * it is not possible to assign `null` to a variable of type [[scala.Int]]. + */ +sealed trait Null diff --git a/test/scaladoc/run/SI-5780.check b/test/scaladoc/run/SI-5780.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-5780.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-5780.scala b/test/scaladoc/run/SI-5780.scala new file mode 100644 index 0000000000..809567faec --- /dev/null +++ b/test/scaladoc/run/SI-5780.scala @@ -0,0 +1,25 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.SI5780 + + object `package` { def foo: AnyRef = "hello"; class T /* so the package is not dropped */ } + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "-doc-root-content " + resourcePath + "/doc-root" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + val foo = rootPackage._package("scala")._package("test")._package("scaladoc")._package("SI5780")._method("foo") + // check that AnyRef is properly linked to its template: + assert(foo.resultType.name == "AnyRef", foo.resultType.name + " == AnyRef") + assert(foo.resultType.refEntity.size == 1, foo.resultType.refEntity + ".size == 1") + } +} \ No newline at end of file -- cgit v1.2.3 From 4b6ae392a7aaf147de3991998d52be5e7b7e665e Mon Sep 17 00:00:00 2001 From: Mirco Dotta Date: Mon, 25 Jun 2012 16:57:04 +0200 Subject: Enhanced presentation compiler test infrastructure * Removed unneeded .flags. * Renamed a few methods in `InteractiveTest`. * Force the presentation compiler to shut down after each test. * -sourcepath in the .flags file is now relative to the test's base directory. * Use `InteractiveReporter` in Presentation Compiler tests. By using the `InteractiveReporter`, compilation errors are collected in the compilation unit. This was necessary for testing SI-5975. --- .../tools/nsc/interactive/tests/InteractiveTest.scala | 14 +++++++++----- .../interactive/tests/InteractiveTestSettings.scala | 9 ++++++--- .../tests/core/PresentationCompilerInstance.scala | 9 ++++++--- test/files/presentation/hyperlinks.flags | 2 -- test/files/presentation/hyperlinks/Runner.scala | 4 ++-- test/files/presentation/ide-bug-1000469/Runner.scala | 4 +--- test/files/presentation/ide-bug-1000531.flags | 18 ------------------ .../presentation/memory-leaks/MemoryLeaksTest.scala | 5 +---- test/files/presentation/t5708/Test.scala | 4 +--- test/files/presentation/visibility/Test.scala | 4 +--- 10 files changed, 27 insertions(+), 46 deletions(-) delete mode 100644 test/files/presentation/hyperlinks.flags delete mode 100644 test/files/presentation/ide-bug-1000531.flags diff --git a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala index f622f11ffd..afb8985700 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala @@ -82,13 +82,17 @@ abstract class InteractiveTest /** Test's entry point */ def main(args: Array[String]) { + try execute() + finally shutdown() + } + + protected def execute(): Unit = { loadSources() - runTests() - shutdown() + runDefaultTests() } /** Load all sources before executing the test. */ - private def loadSources() { + protected def loadSources() { // ask the presentation compiler to track all sources. We do // not wait for the file to be entirely typed because we do want // to exercise the presentation compiler on scoped type requests. @@ -100,7 +104,7 @@ abstract class InteractiveTest } /** Run all defined `PresentationCompilerTestDef` */ - protected def runTests() { + protected def runDefaultTests() { //TODO: integrate random tests!, i.e.: if (runRandomTests) randomTests(20, sourceFiles) testActions.foreach(_.runTest()) } @@ -109,7 +113,7 @@ abstract class InteractiveTest private def randomTests(n: Int, files: Array[SourceFile]) { val tester = new Tester(n, files, settings) { override val compiler = self.compiler - override val reporter = compilerReporter + override val reporter = new reporters.StoreReporter } tester.run() } diff --git a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala index 36671555d1..4d85ab9d88 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala @@ -4,10 +4,8 @@ package tests import java.io.File.pathSeparatorChar import java.io.File.separatorChar - import scala.tools.nsc.interactive.tests.core.PresentationCompilerInstance -import scala.tools.nsc.io.File - +import scala.tools.nsc.io.{File,Path} import core.Reporter import core.TestSettings @@ -46,6 +44,11 @@ trait InteractiveTestSettings extends TestSettings with PresentationCompilerInst println("error processing arguments (unprocessed: %s)".format(rest)) case _ => () } + + // Make the --sourcepath path provided in the .flags file (if any) relative to the test's base directory + if(settings.sourcepath.isSetByUser) + settings.sourcepath.value = (baseDir / Path(settings.sourcepath.value)).path + adjustPaths(settings.bootclasspath, settings.classpath, settings.javabootclasspath, settings.sourcepath) } diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala index 8ccb5aa075..5c1837b3bf 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala @@ -2,12 +2,15 @@ package scala.tools.nsc package interactive package tests.core -import reporters.StoreReporter +import reporters.{Reporter => CompilerReporter} +import scala.reflect.internal.util.Position /** Trait encapsulating the creation of a presentation compiler's instance.*/ -private[tests] trait PresentationCompilerInstance { +private[tests] trait PresentationCompilerInstance extends TestSettings { protected val settings = new Settings - protected val compilerReporter = new StoreReporter + protected val compilerReporter: CompilerReporter = new InteractiveReporter { + override def compiler = PresentationCompilerInstance.this.compiler + } protected lazy val compiler: Global = { prepareSettings(settings) diff --git a/test/files/presentation/hyperlinks.flags b/test/files/presentation/hyperlinks.flags deleted file mode 100644 index dc13682c5e..0000000000 --- a/test/files/presentation/hyperlinks.flags +++ /dev/null @@ -1,2 +0,0 @@ -# This test will fail in the new pattern matcher because -# it generates trees whose positions are not transparent diff --git a/test/files/presentation/hyperlinks/Runner.scala b/test/files/presentation/hyperlinks/Runner.scala index 3d19f2d948..61da49a3d7 100644 --- a/test/files/presentation/hyperlinks/Runner.scala +++ b/test/files/presentation/hyperlinks/Runner.scala @@ -1,11 +1,11 @@ import scala.tools.nsc.interactive.tests.InteractiveTest object Test extends InteractiveTest { - override def runTests() { + override def runDefaultTests() { // make sure typer is done.. the virtual pattern matcher might translate // some trees and mess up positions. But we'll catch it red handed! sourceFiles foreach (src => askLoadedTyped(src).get) - super.runTests() + super.runDefaultTests() } } \ No newline at end of file diff --git a/test/files/presentation/ide-bug-1000469/Runner.scala b/test/files/presentation/ide-bug-1000469/Runner.scala index c53533fddd..1ef3cf9025 100644 --- a/test/files/presentation/ide-bug-1000469/Runner.scala +++ b/test/files/presentation/ide-bug-1000469/Runner.scala @@ -1,5 +1,3 @@ import scala.tools.nsc.interactive.tests._ -object Test extends InteractiveTest { - override val runRandomTests = false -} +object Test extends InteractiveTest \ No newline at end of file diff --git a/test/files/presentation/ide-bug-1000531.flags b/test/files/presentation/ide-bug-1000531.flags deleted file mode 100644 index 56d026a62d..0000000000 --- a/test/files/presentation/ide-bug-1000531.flags +++ /dev/null @@ -1,18 +0,0 @@ -# This file contains command line options that are passed to the presentation compiler -# Lines starting with # are stripped, and you can split arguments on several lines. - -# The -bootclasspath option is treated specially by the test framework: if it's not specified -# in this file, the presentation compiler will pick up the scala-library/compiler that's on the -# java classpath used to run this test (usually build/pack) - -# Any option can be passed this way, like presentation debug -# -Ypresentation-debug -Ypresentation-verbose - -# the classpath is relative to the current working directory. That means it depends where you're -# running partest from. Run it from the root scala checkout for these files to resolve correctly -# (by default when running 'ant test', or 'test/partest'). Paths use Unix separators, the test -# framework translates them to the platform dependent representation. -# -bootclasspath lib/scala-compiler.jar:lib/scala-library.jar:lib/fjbg.jar - -# the following line would test using the quick compiler -# -bootclasspath build/quick/classes/compiler:build/quick/classes/library:lib/fjbg.jar diff --git a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala index 857beac7df..a5533a623a 100644 --- a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala +++ b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala @@ -24,10 +24,7 @@ import scala.tools.nsc.io._ object Test extends InteractiveTest { final val mega = 1024 * 1024 - override def main(args: Array[String]) { - memoryConsumptionTest() - compiler.askShutdown() - } + override def execute(): Unit = memoryConsumptionTest() def batchSource(name: String) = new BatchSourceFile(AbstractFile.getFile(name)) diff --git a/test/files/presentation/t5708/Test.scala b/test/files/presentation/t5708/Test.scala index 96e758d974..bec1131c4c 100644 --- a/test/files/presentation/t5708/Test.scala +++ b/test/files/presentation/t5708/Test.scala @@ -1,5 +1,3 @@ import scala.tools.nsc.interactive.tests.InteractiveTest -object Test extends InteractiveTest { - -} \ No newline at end of file +object Test extends InteractiveTest \ No newline at end of file diff --git a/test/files/presentation/visibility/Test.scala b/test/files/presentation/visibility/Test.scala index 96e758d974..bec1131c4c 100644 --- a/test/files/presentation/visibility/Test.scala +++ b/test/files/presentation/visibility/Test.scala @@ -1,5 +1,3 @@ import scala.tools.nsc.interactive.tests.InteractiveTest -object Test extends InteractiveTest { - -} \ No newline at end of file +object Test extends InteractiveTest \ No newline at end of file -- cgit v1.2.3 From 70503355299263f95a3447701bb483375bf46665 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 2 Jul 2012 17:25:18 +0200 Subject: Allow attachments for symbols, just like for trees. Removes the two global hash maps in Namers, and the one in NamesDefaults. Also fixes SI-5975. --- .../scala/tools/nsc/typechecker/Namers.scala | 73 ++++++++-------------- .../tools/nsc/typechecker/NamesDefaults.scala | 15 ++++- .../scala/tools/nsc/typechecker/Typers.scala | 1 - .../scala/tools/nsc/typechecker/Unapplies.scala | 8 +++ .../scala/reflect/internal/StdAttachments.scala | 17 ++++- src/reflect/scala/reflect/internal/Symbols.scala | 9 +-- src/reflect/scala/reflect/internal/Trees.scala | 11 +--- src/reflect/scala/reflect/makro/Universe.scala | 24 +++---- test/files/presentation/ide-t1000976.check | 1 + test/files/presentation/ide-t1000976.flags | 1 + test/files/presentation/ide-t1000976/Test.scala | 30 +++++++++ test/files/presentation/ide-t1000976/src/a/A.scala | 7 +++ test/files/presentation/ide-t1000976/src/b/B.scala | 7 +++ test/files/presentation/ide-t1000976/src/c/C.scala | 3 + test/files/presentation/ide-t1000976/src/d/D.scala | 7 +++ 15 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 test/files/presentation/ide-t1000976.check create mode 100644 test/files/presentation/ide-t1000976.flags create mode 100644 test/files/presentation/ide-t1000976/Test.scala create mode 100644 test/files/presentation/ide-t1000976/src/a/A.scala create mode 100644 test/files/presentation/ide-t1000976/src/b/B.scala create mode 100644 test/files/presentation/ide-t1000976/src/c/C.scala create mode 100644 test/files/presentation/ide-t1000976/src/d/D.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index decd18b599..0738bb51d1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -52,27 +52,6 @@ trait Namers extends MethodSynthesis { def newNamerFor(context: Context, tree: Tree): Namer = newNamer(context.makeNewScope(tree, tree.symbol)) - // In the typeCompleter (templateSig) of a case class (resp it's module), - // synthetic `copy` (reps `apply`, `unapply`) methods are added. To compute - // their signatures, the corresponding ClassDef is needed. - // During naming, for each case class module symbol, the corresponding ClassDef - // is stored in this map. The map is cleared lazily, i.e. when the new symbol - // is created with the same name, the old one (if present) is wiped out, or the - // entry is deleted when it is used and no longer needed. - private val classOfModuleClass = perRunCaches.newWeakMap[Symbol, WeakReference[ClassDef]]() - - // Default getters of constructors are added to the companion object in the - // typeCompleter of the constructor (methodSig). To compute the signature, - // we need the ClassDef. To create and enter the symbols into the companion - // object, we need the templateNamer of that module class. - // This map is extended during naming of classes, the Namer is added in when - // it's available, i.e. in the type completer (templateSig) of the module class. - private[typechecker] val classAndNamerOfModule = perRunCaches.newMap[Symbol, (ClassDef, Namer)]() - - def resetNamer() { - classAndNamerOfModule.clear() - } - abstract class Namer(val context: Context) extends MethodSynth with NamerContextErrors { thisNamer => import NamerErrorGen._ @@ -621,7 +600,7 @@ trait Namers extends MethodSynthesis { MaxParametersCaseClassError(tree) val m = ensureCompanionObject(tree, caseModuleDef) - classOfModuleClass(m.moduleClass) = new WeakReference(tree) + m.moduleClass.addAttachment(new ClassForCaseCompanionAttachment(tree)) } val hasDefault = impl.body exists { case DefDef(_, nme.CONSTRUCTOR, _, vparamss, _, _) => mexists(vparamss)(_.mods.hasDefault) @@ -629,7 +608,7 @@ trait Namers extends MethodSynthesis { } if (hasDefault) { val m = ensureCompanionObject(tree) - classAndNamerOfModule(m) = (tree, null) + m.addAttachment(new ConstructorDefaultsAttachment(tree, null)) } val owner = tree.symbol.owner if (settings.lint.value && owner.isPackageObjectClass && !mods.isImplicit) { @@ -660,7 +639,8 @@ trait Namers extends MethodSynthesis { if (sym.isLazy) sym.lazyAccessor andAlso enterIfNotThere - defaultParametersOfMethod(sym) foreach { symRef => enterIfNotThere(symRef()) } + for (defAtt <- sym.attachments.get[DefaultsOfLocalMethodAttachment]) + defAtt.defaultGetters foreach enterIfNotThere } this.context } @@ -849,23 +829,20 @@ trait Namers extends MethodSynthesis { // add apply and unapply methods to companion objects of case classes, // unless they exist already; here, "clazz" is the module class if (clazz.isModuleClass) { - Namers.this.classOfModuleClass get clazz foreach { cdefRef => - val cdef = cdefRef() - if (cdef.mods.isCase) addApplyUnapply(cdef, templateNamer) - classOfModuleClass -= clazz + clazz.attachments.get[ClassForCaseCompanionAttachment] foreach { cma => + val cdef = cma.caseClass + assert(cdef.mods.isCase, "expected case class: "+ cdef) + addApplyUnapply(cdef, templateNamer) } } // add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because // the namer phase must traverse this copy method to create default getters for its parameters. // here, clazz is the ClassSymbol of the case class (not the module). - // @check: this seems to work only if the type completer of the class runs before the one of the - // module class: the one from the module class removes the entry from classOfModuleClass (see above). if (clazz.isClass && !clazz.hasModuleFlag) { val modClass = companionSymbolOf(clazz, context).moduleClass - Namers.this.classOfModuleClass get modClass map { cdefRef => - val cdef = cdefRef() - + modClass.attachments.get[ClassForCaseCompanionAttachment] foreach { cma => + val cdef = cma.caseClass def hasCopy(decls: Scope) = (decls lookup nme.copy) != NoSymbol if (cdef.mods.isCase && !hasCopy(decls) && !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && @@ -877,9 +854,8 @@ trait Namers extends MethodSynthesis { // if default getters (for constructor defaults) need to be added to that module, here's the namer // to use. clazz is the ModuleClass. sourceModule works also for classes defined in methods. val module = clazz.sourceModule - classAndNamerOfModule get module foreach { - case (cdef, _) => - classAndNamerOfModule(module) = (cdef, templateNamer) + for (cda <- module.attachments.get[ConstructorDefaultsAttachment]) { + cda.companionModuleClassNamer = templateNamer } ClassInfoType(parents, decls, clazz) } @@ -1100,13 +1076,15 @@ trait Namers extends MethodSynthesis { val module = companionSymbolOf(clazz, context) module.initialize // call type completer (typedTemplate), adds the // module's templateNamer to classAndNamerOfModule - classAndNamerOfModule get module match { - case s @ Some((cdef, nmr)) if nmr != null => - moduleNamer = s - (cdef, nmr) + module.attachments.get[ConstructorDefaultsAttachment] match { + // by martin: the null case can happen in IDE; this is really an ugly hack on top of an ugly hack but it seems to work + // later by lukas: disabled when fixing SI-5975, i think it cannot happen anymore + case Some(cda) /*if cma.companionModuleClassNamer == null*/ => + val p = (cda.classWithDefault, cda.companionModuleClassNamer) + moduleNamer = Some(p) + p case _ => return // fix #3649 (prevent crash in erroneous source code) - // nmr == null can happen in IDE; this is really an ugly hack on top[ of an ugly hack but it seems to work } } deftParams = cdef.tparams map copyUntypedInvariant @@ -1144,11 +1122,14 @@ trait Namers extends MethodSynthesis { clazz.resetFlag(INTERFACE) // there's a concrete member now val default = parentNamer.enterSyntheticSym(defaultTree) if (forInteractive && default.owner.isTerm) { - // enter into map from method symbols to default arguments. - // if compiling the same local block several times (which can happen in interactive mode) - // we might otherwise not find the default symbol, because the second time it the - // method symbol will be re-entered in the scope but the default parameter will not. - defaultParametersOfMethod(meth) += new WeakReference(default) + // save the default getters as attachments in the method symbol. if compiling the + // same local block several times (which can happen in interactive mode) we might + // otherwise not find the default symbol, because the second time it the method + // symbol will be re-entered in the scope but the default parameter will not. + val att = meth.attachments.get[DefaultsOfLocalMethodAttachment] match { + case Some(att) => att.defaultGetters += default + case None => meth.addAttachment(new DefaultsOfLocalMethodAttachment(default)) + } } } else if (baseHasDefault) { // the parameter does not have a default itself, but the diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 61443faba0..a0c1342026 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -21,8 +21,19 @@ trait NamesDefaults { self: Analyzer => import definitions._ import NamesDefaultsErrorsGen._ - val defaultParametersOfMethod = - perRunCaches.newWeakMap[Symbol, Set[WeakReference[Symbol]]]() withDefaultValue Set() + // Default getters of constructors are added to the companion object in the + // typeCompleter of the constructor (methodSig). To compute the signature, + // we need the ClassDef. To create and enter the symbols into the companion + // object, we need the templateNamer of that module class. These two are stored + // as an attachment in the companion module symbol + class ConstructorDefaultsAttachment(val classWithDefault: ClassDef, var companionModuleClassNamer: Namer) + + // To attach the default getters of local (term-owned) methods to the method symbol. + // Used in Namer.enterExistingSym: it needs to re-enter the method symbol and also + // default getters, which could not be found otherwise. + class DefaultsOfLocalMethodAttachment(val defaultGetters: mutable.Set[Symbol]) { + def this(default: Symbol) = this(mutable.Set(default)) + } case class NamedApplyInfo( qual: Option[Tree], diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 52cce11deb..f44b134fc6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -47,7 +47,6 @@ trait Typers extends Modes with Adaptations with Tags { def resetTyper() { //println("resetTyper called") resetContexts() - resetNamer() resetImplicits() transformed.clear() } diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 4c20d14406..e70a6685bd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -23,6 +23,14 @@ trait Unapplies extends ast.TreeDSL private val unapplyParamName = nme.x_0 + + // In the typeCompleter (templateSig) of a case class (resp it's module), + // synthetic `copy` (reps `apply`, `unapply`) methods are added. To compute + // their signatures, the corresponding ClassDef is needed. During naming (in + // `enterClassDef`), the case class ClassDef is added as an attachment to the + // moduleClass symbol of the companion module. + class ClassForCaseCompanionAttachment(val caseClass: ClassDef) + /** returns type list for return type of the extraction */ def unapplyTypeList(ufn: Symbol, ufntpe: Type) = { assert(ufn.isMethod, ufn) diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 4ea9b27da9..60b3a6f436 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -4,9 +4,24 @@ package internal trait StdAttachments { self: SymbolTable => + /** + * Common code between reflect-internal Symbol and Tree related to Attachments. + */ + trait Attachable { + protected var rawatt: base.Attachments { type Pos = Position } = NoPosition + def attachments = rawatt + def addAttachment(attachment: Any): this.type = { rawatt = rawatt.add(attachment); this } + def removeAttachment[T: ClassTag]: this.type = { rawatt = rawatt.remove[T]; this } + + // cannot be final due to SynchronizedSymbols + def pos: Position = rawatt.pos + def pos_=(pos: Position): Unit = rawatt = (rawatt withPos pos) + def setPos(newpos: Position): this.type = { pos = newpos; this } + } + case object BackquotedIdentifierAttachment case class CompoundTypeTreeOriginalAttachment(parents: List[Tree], stats: List[Tree]) case class MacroExpansionAttachment(original: Tree) -} \ No newline at end of file +} diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 79041924a8..f0c6252dd9 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -10,6 +10,7 @@ import scala.collection.{ mutable, immutable } import scala.collection.mutable.ListBuffer import util.Statistics import Flags._ +import base.Attachments trait Symbols extends api.Symbols { self: SymbolTable => import definitions._ @@ -154,7 +155,8 @@ trait Symbols extends api.Symbols { self: SymbolTable => abstract class Symbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: Name) extends SymbolContextApiImpl with HasFlags - with Annotatable[Symbol] { + with Annotatable[Symbol] + with Attachable { type AccessBoundaryType = Symbol type AnnotationType = AnnotationInfo @@ -176,7 +178,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => def rawowner = _rawowner def rawflags = _rawflags - private var rawpos = initPos + rawatt = initPos val id = nextId() // identity displayed when -uniqid //assert(id != 3390, initName) @@ -189,8 +191,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => def validTo = _validTo def validTo_=(x: Period) { _validTo = x} - def pos = rawpos - def setPos(pos: Position): this.type = { this.rawpos = pos; this } def setName(name: Name): this.type = { this.name = asNameType(name) ; this } // Update the surrounding scopes @@ -1616,6 +1616,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => setInfo (this.info cloneInfo clone) setAnnotations this.annotations ) + this.attachments.all.foreach(clone.addAttachment) if (clone.thisSym != clone) clone.typeOfThis = (clone.typeOfThis cloneInfo clone) diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index dd13dd4c4c..e92d644f4a 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -15,20 +15,13 @@ trait Trees extends api.Trees { self: SymbolTable => private[scala] var nodeCount = 0 - abstract class Tree extends TreeContextApiImpl with Product { + abstract class Tree extends TreeContextApiImpl with Attachable with Product { val id = nodeCount // TODO: add to attachment? nodeCount += 1 Statistics.incCounter(TreesStats.nodeByType, getClass) - @inline final def pos: Position = rawatt.pos - def pos_=(pos: Position): Unit = rawatt = (rawatt withPos pos) - def setPos(newpos: Position): this.type = { pos = newpos; this } - - private var rawatt: Attachments { type Pos = Position } = NoPosition - def attachments = rawatt - def addAttachment(attachment: Any): this.type = { rawatt = rawatt.add(attachment); this } - def removeAttachment[T: ClassTag]: this.type = { rawatt = rawatt.remove[T]; this } + @inline final override def pos: Position = rawatt.pos private[this] var rawtpe: Type = _ @inline final def tpe = rawtpe diff --git a/src/reflect/scala/reflect/makro/Universe.scala b/src/reflect/scala/reflect/makro/Universe.scala index 98046be555..a676f7f1de 100644 --- a/src/reflect/scala/reflect/makro/Universe.scala +++ b/src/reflect/scala/reflect/makro/Universe.scala @@ -5,13 +5,24 @@ abstract class Universe extends scala.reflect.api.Universe { val treeBuild: TreeBuilder { val global: Universe.this.type } + trait AttachableApi { + /** ... */ + def attachments: base.Attachments { type Pos = Position } + + /** ... */ + def addAttachment(attachment: Any): AttachableApi.this.type + + /** ... */ + def removeAttachment[T: ClassTag]: AttachableApi.this.type + } + // Symbol extensions --------------------------------------------------------------- override type Symbol >: Null <: SymbolContextApi /** The extended API of symbols that's supported in macro context universes */ - trait SymbolContextApi extends SymbolApi { this: Symbol => + trait SymbolContextApi extends SymbolApi with AttachableApi { this: Symbol => // [Eugene++ to Martin] should we also add mutability methods here (similarly to what's done below for trees)? // I'm talking about `setAnnotations` and friends @@ -23,7 +34,7 @@ abstract class Universe extends scala.reflect.api.Universe { /** The extended API of trees that's supported in macro context universes */ - trait TreeContextApi extends TreeApi { this: Tree => + trait TreeContextApi extends TreeApi with AttachableApi { this: Tree => /** ... */ def pos_=(pos: Position): Unit @@ -62,15 +73,6 @@ abstract class Universe extends scala.reflect.api.Universe { /** ... */ def setSymbol(sym: Symbol): this.type - - /** ... */ - def attachments: base.Attachments { type Pos = Position } - - /** ... */ - def addAttachment(attachment: Any): this.type - - /** ... */ - def removeAttachment[T: ClassTag]: this.type } override type SymTree >: Null <: Tree with SymTreeContextApi diff --git a/test/files/presentation/ide-t1000976.check b/test/files/presentation/ide-t1000976.check new file mode 100644 index 0000000000..d58f86d6c6 --- /dev/null +++ b/test/files/presentation/ide-t1000976.check @@ -0,0 +1 @@ +Test OK \ No newline at end of file diff --git a/test/files/presentation/ide-t1000976.flags b/test/files/presentation/ide-t1000976.flags new file mode 100644 index 0000000000..9a1a05a4f6 --- /dev/null +++ b/test/files/presentation/ide-t1000976.flags @@ -0,0 +1 @@ +-sourcepath src \ No newline at end of file diff --git a/test/files/presentation/ide-t1000976/Test.scala b/test/files/presentation/ide-t1000976/Test.scala new file mode 100644 index 0000000000..722259d3a1 --- /dev/null +++ b/test/files/presentation/ide-t1000976/Test.scala @@ -0,0 +1,30 @@ +import scala.tools.nsc.interactive.tests.InteractiveTest +import scala.reflect.internal.util.SourceFile +import scala.tools.nsc.interactive.Response + +object Test extends InteractiveTest { + override def execute(): Unit = { + loadSourceAndWaitUntilTypechecked("A.scala") + val sourceB = loadSourceAndWaitUntilTypechecked("B.scala") + checkErrors(sourceB) + } + + private def loadSourceAndWaitUntilTypechecked(sourceName: String): SourceFile = { + val sourceFile = sourceFiles.find(_.file.name == sourceName).head + compiler.askToDoFirst(sourceFile) + val res = new Response[Unit] + compiler.askReload(List(sourceFile), res) + res.get + askLoadedTyped(sourceFile).get + sourceFile + } + + private def checkErrors(source: SourceFile): Unit = compiler.getUnitOf(source) match { + case Some(unit) => + val problems = unit.problems.toList + if(problems.isEmpty) reporter.println("Test OK") + else problems.foreach(problem => reporter.println(problem.msg)) + + case None => reporter.println("No compilation unit found for " + source.file.name) + } +} diff --git a/test/files/presentation/ide-t1000976/src/a/A.scala b/test/files/presentation/ide-t1000976/src/a/A.scala new file mode 100644 index 0000000000..fcfef8b525 --- /dev/null +++ b/test/files/presentation/ide-t1000976/src/a/A.scala @@ -0,0 +1,7 @@ +package a + +import d.D._ + +object A { + Seq.empty[Byte].toArray.toSeq +} diff --git a/test/files/presentation/ide-t1000976/src/b/B.scala b/test/files/presentation/ide-t1000976/src/b/B.scala new file mode 100644 index 0000000000..628348cac1 --- /dev/null +++ b/test/files/presentation/ide-t1000976/src/b/B.scala @@ -0,0 +1,7 @@ +package b + +import c.C + +class B { + new C("") +} diff --git a/test/files/presentation/ide-t1000976/src/c/C.scala b/test/files/presentation/ide-t1000976/src/c/C.scala new file mode 100644 index 0000000000..cc23e3eef1 --- /dev/null +++ b/test/files/presentation/ide-t1000976/src/c/C.scala @@ -0,0 +1,3 @@ +package c + +class C(key: String = "", componentStates: String = "") diff --git a/test/files/presentation/ide-t1000976/src/d/D.scala b/test/files/presentation/ide-t1000976/src/d/D.scala new file mode 100644 index 0000000000..d7a48f98d5 --- /dev/null +++ b/test/files/presentation/ide-t1000976/src/d/D.scala @@ -0,0 +1,7 @@ +package d + +import c.C + +object D { + implicit def c2s(c: C): String = "" +} -- cgit v1.2.3 From 16702363dbf3a305b9a7f04c04df18f58f2a3b15 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 5 Jul 2012 17:19:51 +0200 Subject: SI-5974 make collection.convert.Wrappers serializable --- src/library/scala/collection/convert/Wrappers.scala | 3 ++- test/files/run/t5974.check | 1 + test/files/run/t5974.scala | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t5974.check create mode 100644 test/files/run/t5974.scala diff --git a/src/library/scala/collection/convert/Wrappers.scala b/src/library/scala/collection/convert/Wrappers.scala index 8c603dc91b..75707b69b0 100644 --- a/src/library/scala/collection/convert/Wrappers.scala +++ b/src/library/scala/collection/convert/Wrappers.scala @@ -467,4 +467,5 @@ private[collection] trait Wrappers { } } -object Wrappers extends Wrappers +@SerialVersionUID(0 - 5857859809262781311L) +object Wrappers extends Wrappers with Serializable diff --git a/test/files/run/t5974.check b/test/files/run/t5974.check new file mode 100644 index 0000000000..9766475a41 --- /dev/null +++ b/test/files/run/t5974.check @@ -0,0 +1 @@ +ok diff --git a/test/files/run/t5974.scala b/test/files/run/t5974.scala new file mode 100644 index 0000000000..5b99e9f721 --- /dev/null +++ b/test/files/run/t5974.scala @@ -0,0 +1,10 @@ +object Test extends App { + import scala.collection.JavaConverters._ + + def ser(a: AnyRef) = + (new java.io.ObjectOutputStream(new java.io.ByteArrayOutputStream())).writeObject(a) + + val l = java.util.Arrays.asList("pigdog").asScala + ser(l) + println("ok") +} -- cgit v1.2.3 From 28ce397cce15d575959b42097ab69489ddd83b46 Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Thu, 5 Jul 2012 10:53:21 -0500 Subject: Fixed the API documentation for scala.collection.MapLike.filterNot. The previous documentation erroneously indicated that filterNot retains elements for which the supplied predicate is true when in fact the opposite is the case, as verified in the REPL. --- src/library/scala/collection/MapLike.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library/scala/collection/MapLike.scala b/src/library/scala/collection/MapLike.scala index 55d482f6c8..ed2a877631 100644 --- a/src/library/scala/collection/MapLike.scala +++ b/src/library/scala/collection/MapLike.scala @@ -301,11 +301,11 @@ self => def ++[B1 >: B](xs: GenTraversableOnce[(A, B1)]): Map[A, B1] = ((repr: Map[A, B1]) /: xs.seq) (_ + _) - /** Returns a new map with all key/value pairs for which the predicate + /** Returns a new map obtained by removing all key/value pairs for which the predicate * `p` returns `true`. * - * '''Note:''' This method works by successively removing elements fro which the - * predicate is false from this set. + * '''Note:''' This method works by successively removing elements for which the + * predicate is true from this set. * If removal is slow, or you expect that most elements of the set * will be removed, you might consider using `filter` * with a negated predicate instead. -- cgit v1.2.3 From 494ee668bec03182822b022f805dd38c6e062e60 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 6 Jul 2012 08:24:50 -0400 Subject: Fix to push-jars to use same sha fixing that pull uses. --- tools/binary-repo-lib.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/binary-repo-lib.sh b/tools/binary-repo-lib.sh index a22747520c..64f62a103d 100755 --- a/tools/binary-repo-lib.sh +++ b/tools/binary-repo-lib.sh @@ -75,24 +75,21 @@ pushJarFile() { local jar_dir=$(dirname $jar) local jar_name=${jar#$jar_dir/} pushd $jar_dir >/dev/null - local jar_sha1=$(shasum -p $jar_name) - local version=${jar_sha1% ?$jar_name} + local version=$(makeJarSha $jar_name) local remote_uri=${version}${jar#$basedir} echo " Pushing to ${remote_urlbase}/${remote_uri} ..." echo " $curl" curlUpload $remote_uri $jar_name $user $pw echo " Making new sha1 file ...." - echo "$jar_sha1" > "${jar_name}${desired_ext}" + echo "$version ?$jar_name" > "${jar_name}${desired_ext}" popd >/dev/null # TODO - Git remove jar and git add jar.desired.sha1 # rm $jar } -getJarSha() { +makeJarSha() { local jar=$1 - if [[ ! -f "$jar" ]]; then - echo "" - elif which sha1sum 2>/dev/null >/dev/null; then + if which sha1sum 2>/dev/null >/dev/null; then shastring=$(sha1sum "$jar") echo "$shastring" | sed 's/ .*//' elif which shasum 2>/dev/null >/dev/null; then @@ -104,6 +101,15 @@ getJarSha() { fi } +getJarSha() { + local jar=$1 + if [[ ! -f "$jar" ]]; then + echo "" + else + echo $(makeJarSha $jar) + fi +} + # Tests whether or not the .desired.sha1 hash matches a given file. # Arugment 1 - The jar file to test validity. # Returns: Empty string on failure, "OK" on success. -- cgit v1.2.3 From 5db7d7887ac0c86cb0160bddc5e1294fddb25bf6 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 5 Jul 2012 18:23:18 +0200 Subject: SI-2442 sealedness for java enums non-experimental --- src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala | 4 ++-- test/files/neg/t2442.flags | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 046b177444..65b0ff1e6d 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -613,8 +613,8 @@ abstract class ClassfileParser { parseAttributes(sym, info) getScope(jflags).enter(sym) - // sealed java enums (experimental) - if (isEnum && opt.experimental) { + // sealed java enums + if (isEnum) { val enumClass = sym.owner.linkedClassOfClass if (!enumClass.isSealed) enumClass setFlag (SEALED | ABSTRACT) diff --git a/test/files/neg/t2442.flags b/test/files/neg/t2442.flags index 32cf036c3d..e8fb65d50c 100644 --- a/test/files/neg/t2442.flags +++ b/test/files/neg/t2442.flags @@ -1 +1 @@ --Xexperimental -Xfatal-warnings \ No newline at end of file +-Xfatal-warnings \ No newline at end of file -- cgit v1.2.3 From 623b739bde7abce5271416fbd07e91dd547fd33f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 6 Jul 2012 14:04:27 +0200 Subject: macro-based string interpolation formatter This commit provides a macro based string interpolation formatter. The macro is implemented in MacroImplementations.scala. In order to still be able to build a new STARR, the implementation in StringContext.f is not yet changed. This will be replaced in a later commit. --- src/compiler/scala/tools/reflect/FastTrack.scala | 6 +- .../scala/tools/reflect/MacroImplementations.scala | 148 +++++++++++++++++++++ src/library/scala/StringContext.scala | 13 +- .../scala/reflect/internal/Definitions.scala | 3 + src/reflect/scala/reflect/internal/StdNames.scala | 1 + 5 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 src/compiler/scala/tools/reflect/MacroImplementations.scala diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 59160f87d0..8ea66979bc 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -15,8 +15,9 @@ trait FastTrack { import definitions._ import language.implicitConversions - private implicit def context2taggers(c0: MacroContext) : Taggers { val c: c0.type } = new { val c: c0.type = c0 } with Taggers - private implicit def context2contextreifiers(c0: MacroContext) : ContextReifiers { val c: c0.type } = new { val c: c0.type = c0 } with ContextReifiers + private implicit def context2taggers(c0: MacroContext): Taggers { val c: c0.type } = new { val c: c0.type = c0 } with Taggers + private implicit def context2contextreifiers(c0: MacroContext): ContextReifiers { val c: c0.type } = new { val c: c0.type = c0 } with ContextReifiers + private implicit def context2macroimplementations(c0: MacroContext): MacroImplementations { val c: c0.type } = new { val c: c0.type = c0 } with MacroImplementations implicit def fastTrackEntry2MacroRuntime(entry: FastTrackEntry): MacroRuntime = args => entry.run(args) type FastTrackExpander = PartialFunction[(MacroContext, Tree), Tree] @@ -42,6 +43,7 @@ trait FastTrack { ApiUniverseReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExpr(c.prefix.tree, EmptyTree, expr) } MacroContextReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExprForMacroContext(c.prefix.tree, expr) } ReflectRuntimeCurrentMirror bindTo { case (c, _) => scala.reflect.runtime.Macros.currentMirror(c).tree } + StringContext_f bindTo { case (c, Apply(Select(Apply(_, parts), _), args)) => c.macro_StringInterpolation_f(parts, args) } registry } } \ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/MacroImplementations.scala b/src/compiler/scala/tools/reflect/MacroImplementations.scala new file mode 100644 index 0000000000..8e5f4d8d04 --- /dev/null +++ b/src/compiler/scala/tools/reflect/MacroImplementations.scala @@ -0,0 +1,148 @@ +package scala.tools.reflect + +import scala.reflect.makro.{ReificationError, UnexpectedReificationError} +import scala.reflect.makro.runtime.Context +import scala.collection.mutable.ListBuffer +import scala.collection.mutable.Stack + +abstract class MacroImplementations { + val c: Context + + import c.universe._ + + def macro_StringInterpolation_f(parts: List[Tree], args: List[Tree]): Tree = { + // the parts all have the same position information (as the expression is generated by the compiler) + // the args have correct position information + + // the following conditions can only be violated if invoked directly + if (parts.length != args.length + 1) { + if(parts.length == 0) + c.abort(c.prefix.tree.pos, "too few parts") + else if(args.length + 1 < parts.length) + c.abort(if(args.length==0) c.enclosingPosition else args.last.pos, + "too few arguments for interpolated string") + else + c.abort(args(parts.length-1).pos, + "too many arguments for interpolated string") + } + + val stringParts = parts map { + case Literal(Constant(s: String)) => s; + case _ => throw new IllegalArgumentException("argument parts must be a list of string literals") + } + + val pi = stringParts.iterator + val bldr = new java.lang.StringBuilder + val evals = ListBuffer[ValDef]() + val ids = ListBuffer[Ident]() + val argsStack = Stack(args : _*) + + def defval(value: Tree, tpe: Type): Unit = { + val freshName = newTermName(c.fresh("arg$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + ids += Ident(freshName) + } + + def isFlag(ch: Char): Boolean = { + ch match { + case '-' | '#' | '+' | ' ' | '0' | ',' | '(' => true + case _ => false + } + } + + def checkType(arg: Tree, variants: Type*): Option[Type] = { + variants.find(arg.tpe <:< _).orElse( + variants.find(c.inferImplicitView(arg, arg.tpe, _) != EmptyTree).orElse( + Some(variants(0)) + ) + ) + } + + def conversionType(ch: Char, arg: Tree): Option[Type] = { + ch match { + case 'b' | 'B' => + if(arg.tpe <:< NullTpe) Some(NullTpe) else Some(BooleanTpe) + case 'h' | 'H' => + Some(AnyTpe) + case 's' | 'S' => + Some(AnyTpe) + case 'c' | 'C' => + checkType(arg, CharTpe, ByteTpe, ShortTpe, IntTpe) + case 'd' | 'o' | 'x' | 'X' => + checkType(arg, IntTpe, LongTpe, ByteTpe, ShortTpe, typeOf[BigInt], typeOf[java.math.BigInteger]) + // BigInteger can be removed as soon as an implicit conversion is defined from BigInteger to BigInt + case 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' => + checkType(arg, DoubleTpe, FloatTpe, typeOf[BigDecimal]) + case 't' | 'T' => + checkType(arg, LongTpe, typeOf[java.util.Calendar], typeOf[java.util.Date]) + case _ => None + } + } + + def copyString(first: Boolean): Unit = { + val str = StringContext.treatEscapes(pi.next()) + val strLen = str.length + val strIsEmpty = strLen == 0 + var start = 0 + var idx = 0 + + if (!first) { + val arg = argsStack.pop + if (strIsEmpty || (str charAt 0) != '%') { + bldr append "%s" + defval(arg, AnyTpe) + } else { + // PRE str is not empty and str(0) == '%' + // argument index parameter is not allowed, thus parse + // [flags][width][.precision]conversion + var pos = 1 + while(pos < strLen && isFlag(str charAt pos)) pos += 1 + while(pos < strLen && Character.isDigit(str charAt pos)) pos += 1 + if(pos < strLen && str.charAt(pos) == '.') { pos += 1 + while(pos < strLen && Character.isDigit(str charAt pos)) pos += 1 + } + if(pos < strLen) { + conversionType(str charAt pos, arg) match { + case Some(tpe) => defval(arg, tpe) + case None => c.error(arg.pos, "illegal conversion character") + } + } else { + // TODO: place error message on conversion string + c.error(arg.pos, "wrong conversion string") + } + } + idx = 1 + } + if (!strIsEmpty) { + val len = str.length + while (idx < len) { + if (str(idx) == '%') { + bldr append (str substring (start, idx)) append "%%" + start = idx + 1 + } + idx += 1 + } + bldr append (str substring (start, idx)) + } + } + + copyString(first = true) + while (pi.hasNext) { + copyString(first = false) + } + + val fstring = bldr.toString +// val expr = c.reify(fstring.format((ids.map(id => Expr(id).eval)) : _*)) +// https://issues.scala-lang.org/browse/SI-5824, therefore + val expr = + Apply( + Select( + Literal(Constant(fstring)), + newTermName("format")), + List(ids: _* ) + ); + + Block(evals.toList, expr) + } + +} \ No newline at end of file diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index f400f18dab..ea58078d93 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -8,7 +8,7 @@ package scala -import collection.mutable.ArrayBuffer +import language.experimental.macros /** A class to support string interpolation. * This class supports string interpolation as outlined in Scala SIP-11. @@ -42,7 +42,7 @@ case class StringContext(parts: String*) { * @throws A `StringContext.InvalidEscapeException` if if a `parts` string contains a backslash (`\`) character * that does not start a valid escape sequence. */ - def s(args: Any*) = { + def s(args: Any*): String = { checkLengths(args: _*) val pi = parts.iterator val ai = args.iterator @@ -82,7 +82,7 @@ case class StringContext(parts: String*) { * string literally. This is achieved by replacing each such occurrence by the * format specifier `%%`. */ - def f(args: Any*) = { + def f(args: Any*): String = { checkLengths(args: _*) val pi = parts.iterator val bldr = new java.lang.StringBuilder @@ -92,8 +92,9 @@ case class StringContext(parts: String*) { var start = 0 var idx = 0 if (!first) { - if (strIsEmpty || (str charAt 0) != '%') + if (strIsEmpty || (str charAt 0) != '%') { bldr append "%s" + } idx = 1 } if (!strIsEmpty) { @@ -114,6 +115,10 @@ case class StringContext(parts: String*) { } bldr.toString format (args: _*) } + +// TODO The above method will be replaced by the following two lines as soon as the new STARR is available +// // The implementation is magically hardwired into `scala.tools.reflect.MacroImplementations.macro_StringInterpolation_f` +// def f(args: Any*): String = macro ??? } object StringContext { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7891433b4f..707663a593 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -496,6 +496,9 @@ trait Definitions extends api.StandardDefinitions { def MacroInternal_materializeAbsTypeTag = getMemberMethod(MacroInternalPackage, nme.materializeAbsTypeTag) def MacroInternal_materializeTypeTag = getMemberMethod(MacroInternalPackage, nme.materializeTypeTag) + lazy val StringContextClass = requiredClass[scala.StringContext] + def StringContext_f = getMemberMethod(StringContextClass, nme.f) + lazy val ScalaSignatureAnnotation = requiredClass[scala.reflect.ScalaSignature] lazy val ScalaLongSignatureAnnotation = requiredClass[scala.reflect.ScalaLongSignature] diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 72a99589d5..22b0908cab 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -662,6 +662,7 @@ trait StdNames { val eval: NameType = "eval" val ex: NameType = "ex" val experimental: NameType = "experimental" + val f: NameType = "f" val false_ : NameType = "false" val filter: NameType = "filter" val finalize_ : NameType = if (forMSIL) "Finalize" else "finalize" -- cgit v1.2.3 From 70f493dc438f464c865e5deb38b46e4e0e773486 Mon Sep 17 00:00:00 2001 From: Dominik Gruntz Date: Fri, 6 Jul 2012 13:08:37 +0200 Subject: adds the sha1 files of the new starr / stringContext.f This commit provides the new sha1 codes of the new STARR. Moreover, it replaces the implementation of StringContext.f to `macro ???`. The implementation is magically hardwired into `scala.tools.reflect.MacroImplementations.macro_StringInterpolation_f` by the new STARR. --- lib/ant/ant-contrib.jar.desired.sha1 | 2 +- lib/ant/ant-dotnet-1.0.jar.desired.sha1 | 2 +- lib/ant/ant.jar.desired.sha1 | 2 +- lib/ant/maven-ant-tasks-2.1.1.jar.desired.sha1 | 2 +- lib/ant/vizant.jar.desired.sha1 | 2 +- lib/fjbg.jar.desired.sha1 | 2 +- lib/forkjoin.jar.desired.sha1 | 2 +- lib/jline.jar.desired.sha1 | 2 +- lib/msil.jar.desired.sha1 | 2 +- lib/scala-compiler.jar.desired.sha1 | 2 +- lib/scala-library-src.jar.desired.sha1 | 2 +- lib/scala-library.jar.desired.sha1 | 2 +- lib/scala-reflect.jar.desired.sha1 | 2 +- src/library/scala/StringContext.scala | 39 ++---------------------- test/files/codelib/code.jar.desired.sha1 | 2 +- test/files/lib/annotations.jar.desired.sha1 | 2 +- test/files/lib/enums.jar.desired.sha1 | 2 +- test/files/lib/genericNest.jar.desired.sha1 | 2 +- test/files/lib/methvsfield.jar.desired.sha1 | 2 +- test/files/lib/nest.jar.desired.sha1 | 2 +- test/files/lib/scalacheck.jar.desired.sha1 | 2 +- test/files/speclib/instrumented.jar.desired.sha1 | 2 +- 22 files changed, 23 insertions(+), 58 deletions(-) diff --git a/lib/ant/ant-contrib.jar.desired.sha1 b/lib/ant/ant-contrib.jar.desired.sha1 index 56745657c5..65bcd122bf 100644 --- a/lib/ant/ant-contrib.jar.desired.sha1 +++ b/lib/ant/ant-contrib.jar.desired.sha1 @@ -1 +1 @@ -943cd5c8802b2a3a64a010efb86ec19bac142e40 ?ant-contrib.jar +943cd5c8802b2a3a64a010efb86ec19bac142e40 *ant-contrib.jar diff --git a/lib/ant/ant-dotnet-1.0.jar.desired.sha1 b/lib/ant/ant-dotnet-1.0.jar.desired.sha1 index 1f1dc9b8c1..d8b6a1ca85 100644 --- a/lib/ant/ant-dotnet-1.0.jar.desired.sha1 +++ b/lib/ant/ant-dotnet-1.0.jar.desired.sha1 @@ -1 +1 @@ -3fc1e35ca8c991fc3488548f7a276bd9053c179d ?ant-dotnet-1.0.jar +3fc1e35ca8c991fc3488548f7a276bd9053c179d *ant-dotnet-1.0.jar diff --git a/lib/ant/ant.jar.desired.sha1 b/lib/ant/ant.jar.desired.sha1 index 852b3397cb..bcb610d6de 100644 --- a/lib/ant/ant.jar.desired.sha1 +++ b/lib/ant/ant.jar.desired.sha1 @@ -1 +1 @@ -7b456ca6b93900f96e58cc8371f03d90a9c1c8d1 ?ant.jar +7b456ca6b93900f96e58cc8371f03d90a9c1c8d1 *ant.jar diff --git a/lib/ant/maven-ant-tasks-2.1.1.jar.desired.sha1 b/lib/ant/maven-ant-tasks-2.1.1.jar.desired.sha1 index 06dcb1e312..53f87c3461 100644 --- a/lib/ant/maven-ant-tasks-2.1.1.jar.desired.sha1 +++ b/lib/ant/maven-ant-tasks-2.1.1.jar.desired.sha1 @@ -1 +1 @@ -7e50e3e227d834695f1e0bf018a7326e06ee4c86 ?maven-ant-tasks-2.1.1.jar +7e50e3e227d834695f1e0bf018a7326e06ee4c86 *maven-ant-tasks-2.1.1.jar diff --git a/lib/ant/vizant.jar.desired.sha1 b/lib/ant/vizant.jar.desired.sha1 index 00ff17d159..998da4643a 100644 --- a/lib/ant/vizant.jar.desired.sha1 +++ b/lib/ant/vizant.jar.desired.sha1 @@ -1 +1 @@ -2c61d6e9a912b3253194d5d6d3e1db7e2545ac4b ?vizant.jar +2c61d6e9a912b3253194d5d6d3e1db7e2545ac4b *vizant.jar diff --git a/lib/fjbg.jar.desired.sha1 b/lib/fjbg.jar.desired.sha1 index d24a5d01fc..6f3ccc77bd 100644 --- a/lib/fjbg.jar.desired.sha1 +++ b/lib/fjbg.jar.desired.sha1 @@ -1 +1 @@ -c3f9b576c91cb9761932ad936ccc4a71f33d2ef2 ?fjbg.jar +8acc87f222210b4a5eb2675477602fc1759e7684 *fjbg.jar diff --git a/lib/forkjoin.jar.desired.sha1 b/lib/forkjoin.jar.desired.sha1 index d31539daf4..8b24962e2c 100644 --- a/lib/forkjoin.jar.desired.sha1 +++ b/lib/forkjoin.jar.desired.sha1 @@ -1 +1 @@ -b5baf94dff8d3ca991d44a59add7bcbf6640379b ?forkjoin.jar +f93a2525b5616d3a4bee7848fabbb2856b56f653 *forkjoin.jar diff --git a/lib/jline.jar.desired.sha1 b/lib/jline.jar.desired.sha1 index 8acb0c9b14..b0426130ac 100644 --- a/lib/jline.jar.desired.sha1 +++ b/lib/jline.jar.desired.sha1 @@ -1 +1 @@ -a5261e70728c1847639e2b47d953441d0b217bcb ?jline.jar +a5261e70728c1847639e2b47d953441d0b217bcb *jline.jar diff --git a/lib/msil.jar.desired.sha1 b/lib/msil.jar.desired.sha1 index 2c2fe79dda..9396b273ab 100644 --- a/lib/msil.jar.desired.sha1 +++ b/lib/msil.jar.desired.sha1 @@ -1 +1 @@ -d48cb950ceded82a5e0ffae8ef2c68d0923ed00c ?msil.jar +d48cb950ceded82a5e0ffae8ef2c68d0923ed00c *msil.jar diff --git a/lib/scala-compiler.jar.desired.sha1 b/lib/scala-compiler.jar.desired.sha1 index 9a47d9f730..b4404c3d24 100644 --- a/lib/scala-compiler.jar.desired.sha1 +++ b/lib/scala-compiler.jar.desired.sha1 @@ -1 +1 @@ -554bcc4a543360c8dc48fde91124dc57319d3460 ?scala-compiler.jar +dacc6e38cdd2eabbf2e4780061c3b4c9e125274d *scala-compiler.jar diff --git a/lib/scala-library-src.jar.desired.sha1 b/lib/scala-library-src.jar.desired.sha1 index 1ec21bc6ee..4711fc583e 100644 --- a/lib/scala-library-src.jar.desired.sha1 +++ b/lib/scala-library-src.jar.desired.sha1 @@ -1 +1 @@ -7db4c7523f0e268ce58de2ab4ae6a3dd0e903f43 ?scala-library-src.jar +0772f79076f74e298e7bf77ad4dfa464c50efe61 *scala-library-src.jar diff --git a/lib/scala-library.jar.desired.sha1 b/lib/scala-library.jar.desired.sha1 index ec73b5f7b8..3f92447c1a 100644 --- a/lib/scala-library.jar.desired.sha1 +++ b/lib/scala-library.jar.desired.sha1 @@ -1 +1 @@ -071d32b24daaeaf961675f248758fedd31a806ed ?scala-library.jar +4edcb1b7030e74259cd906e9230d45a3fffc0444 *scala-library.jar diff --git a/lib/scala-reflect.jar.desired.sha1 b/lib/scala-reflect.jar.desired.sha1 index 4be2e84aef..1f8440cd4a 100644 --- a/lib/scala-reflect.jar.desired.sha1 +++ b/lib/scala-reflect.jar.desired.sha1 @@ -1 +1 @@ -b6e2bbbc1707104119adcb09aeb666690419c424 ?scala-reflect.jar +1187b9c68ca80f951826b1a9b374e8646d9c9a71 *scala-reflect.jar diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index ea58078d93..f11dfb72ae 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -82,43 +82,8 @@ case class StringContext(parts: String*) { * string literally. This is achieved by replacing each such occurrence by the * format specifier `%%`. */ - def f(args: Any*): String = { - checkLengths(args: _*) - val pi = parts.iterator - val bldr = new java.lang.StringBuilder - def copyString(first: Boolean): Unit = { - val str = treatEscapes(pi.next()) - val strIsEmpty = str.length == 0 - var start = 0 - var idx = 0 - if (!first) { - if (strIsEmpty || (str charAt 0) != '%') { - bldr append "%s" - } - idx = 1 - } - if (!strIsEmpty) { - val len = str.length - while (idx < len) { - if (str(idx) == '%') { - bldr append (str substring (start, idx)) append "%%" - start = idx + 1 - } - idx += 1 - } - bldr append (str substring (start, idx)) - } - } - copyString(first = true) - while (pi.hasNext) { - copyString(first = false) - } - bldr.toString format (args: _*) - } - -// TODO The above method will be replaced by the following two lines as soon as the new STARR is available -// // The implementation is magically hardwired into `scala.tools.reflect.MacroImplementations.macro_StringInterpolation_f` -// def f(args: Any*): String = macro ??? + // The implementation is magically hardwired into `scala.tools.reflect.MacroImplementations.macro_StringInterpolation_f` + def f(args: Any*): String = macro ??? } object StringContext { diff --git a/test/files/codelib/code.jar.desired.sha1 b/test/files/codelib/code.jar.desired.sha1 index d2b8d9add9..c4cc74c244 100644 --- a/test/files/codelib/code.jar.desired.sha1 +++ b/test/files/codelib/code.jar.desired.sha1 @@ -1 +1 @@ -e737b123d31eede5594ceda07caafed1673ec472 ?code.jar +e737b123d31eede5594ceda07caafed1673ec472 *code.jar diff --git a/test/files/lib/annotations.jar.desired.sha1 b/test/files/lib/annotations.jar.desired.sha1 index 2b4292d796..ff7bc9425e 100644 --- a/test/files/lib/annotations.jar.desired.sha1 +++ b/test/files/lib/annotations.jar.desired.sha1 @@ -1 +1 @@ -02fe2ed93766323a13f22c7a7e2ecdcd84259b6c ?annotations.jar +02fe2ed93766323a13f22c7a7e2ecdcd84259b6c *annotations.jar diff --git a/test/files/lib/enums.jar.desired.sha1 b/test/files/lib/enums.jar.desired.sha1 index 46cd8e92cf..040dff4487 100644 --- a/test/files/lib/enums.jar.desired.sha1 +++ b/test/files/lib/enums.jar.desired.sha1 @@ -1 +1 @@ -981392dbd1f727b152cd1c908c5fce60ad9d07f7 ?enums.jar +981392dbd1f727b152cd1c908c5fce60ad9d07f7 *enums.jar diff --git a/test/files/lib/genericNest.jar.desired.sha1 b/test/files/lib/genericNest.jar.desired.sha1 index e9321262f2..77e4fec408 100644 --- a/test/files/lib/genericNest.jar.desired.sha1 +++ b/test/files/lib/genericNest.jar.desired.sha1 @@ -1 +1 @@ -b1ec8a095cec4902b3609d74d274c04365c59c04 ?genericNest.jar +b1ec8a095cec4902b3609d74d274c04365c59c04 *genericNest.jar diff --git a/test/files/lib/methvsfield.jar.desired.sha1 b/test/files/lib/methvsfield.jar.desired.sha1 index 8c01532b88..6655f45ddb 100644 --- a/test/files/lib/methvsfield.jar.desired.sha1 +++ b/test/files/lib/methvsfield.jar.desired.sha1 @@ -1 +1 @@ -be8454d5e7751b063ade201c225dcedefd252775 ?methvsfield.jar +be8454d5e7751b063ade201c225dcedefd252775 *methvsfield.jar diff --git a/test/files/lib/nest.jar.desired.sha1 b/test/files/lib/nest.jar.desired.sha1 index 674ca79a5b..056e7ada90 100644 --- a/test/files/lib/nest.jar.desired.sha1 +++ b/test/files/lib/nest.jar.desired.sha1 @@ -1 +1 @@ -cd33e0a0ea249eb42363a2f8ba531186345ff68c ?nest.jar +cd33e0a0ea249eb42363a2f8ba531186345ff68c *nest.jar diff --git a/test/files/lib/scalacheck.jar.desired.sha1 b/test/files/lib/scalacheck.jar.desired.sha1 index e6ed543d73..2f15402d18 100644 --- a/test/files/lib/scalacheck.jar.desired.sha1 +++ b/test/files/lib/scalacheck.jar.desired.sha1 @@ -1 +1 @@ -b6f4dbb29f0c2ec1eba682414f60d52fea84f703 ?scalacheck.jar +b6f4dbb29f0c2ec1eba682414f60d52fea84f703 *scalacheck.jar diff --git a/test/files/speclib/instrumented.jar.desired.sha1 b/test/files/speclib/instrumented.jar.desired.sha1 index 24856fe19a..0b8ee593da 100644 --- a/test/files/speclib/instrumented.jar.desired.sha1 +++ b/test/files/speclib/instrumented.jar.desired.sha1 @@ -1 +1 @@ -474d8c20ab31438d5d4a2ba6bc07ebdcdb530b50 ?instrumented.jar +474d8c20ab31438d5d4a2ba6bc07ebdcdb530b50 *instrumented.jar -- cgit v1.2.3 From 58c053c454dd0cb1146434b97380e4910b6060d7 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 6 Jul 2012 14:05:44 +0200 Subject: stringinterpolation macro test files This commit adds test files neg: checks the error messages generated by the compiler run: checks the macro implementation features --- test/files/neg/stringinterpolation_macro-neg.check | 70 ++++++++++++++ test/files/neg/stringinterpolation_macro-neg.scala | 31 +++++++ test/files/run/stringinterpolation_macro-run.check | 62 +++++++++++++ test/files/run/stringinterpolation_macro-run.scala | 103 +++++++++++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 test/files/neg/stringinterpolation_macro-neg.check create mode 100644 test/files/neg/stringinterpolation_macro-neg.scala create mode 100644 test/files/run/stringinterpolation_macro-run.check create mode 100644 test/files/run/stringinterpolation_macro-run.scala diff --git a/test/files/neg/stringinterpolation_macro-neg.check b/test/files/neg/stringinterpolation_macro-neg.check new file mode 100644 index 0000000000..8986b899a3 --- /dev/null +++ b/test/files/neg/stringinterpolation_macro-neg.check @@ -0,0 +1,70 @@ +stringinterpolation_macro-neg.scala:8: error: too few parts + new StringContext().f() + ^ +stringinterpolation_macro-neg.scala:9: error: too few arguments for interpolated string + new StringContext("", " is ", "%2d years old").f(s) + ^ +stringinterpolation_macro-neg.scala:10: error: too many arguments for interpolated string + new StringContext("", " is ", "%2d years old").f(s, d, d) + ^ +stringinterpolation_macro-neg.scala:11: error: too few arguments for interpolated string + new StringContext("", "").f() + ^ +stringinterpolation_macro-neg.scala:14: error: type mismatch; + found : String + required: Boolean + f"$s%b" + ^ +stringinterpolation_macro-neg.scala:15: error: type mismatch; + found : String + required: Char + f"$s%c" + ^ +stringinterpolation_macro-neg.scala:16: error: type mismatch; + found : Double + required: Char + f"$f%c" + ^ +stringinterpolation_macro-neg.scala:17: error: type mismatch; + found : String + required: Int + f"$s%x" + ^ +stringinterpolation_macro-neg.scala:18: error: type mismatch; + found : Boolean + required: Int + f"$b%d" + ^ +stringinterpolation_macro-neg.scala:19: error: type mismatch; + found : String + required: Int + f"$s%d" + ^ +stringinterpolation_macro-neg.scala:20: error: type mismatch; + found : Double + required: Int + f"$f%o" + ^ +stringinterpolation_macro-neg.scala:21: error: type mismatch; + found : String + required: Double + f"$s%e" + ^ +stringinterpolation_macro-neg.scala:22: error: type mismatch; + found : Boolean + required: Double + f"$b%f" + ^ +stringinterpolation_macro-neg.scala:27: error: type mismatch; + found : String + required: Int +Note that implicit conversions are not applicable because they are ambiguous: + both value strToInt2 of type String => Int + and value strToInt1 of type String => Int + are possible conversion functions from String to Int + f"$s%d" + ^ +stringinterpolation_macro-neg.scala:30: error: illegal conversion character + f"$s%i" + ^ +15 errors found diff --git a/test/files/neg/stringinterpolation_macro-neg.scala b/test/files/neg/stringinterpolation_macro-neg.scala new file mode 100644 index 0000000000..ac9d97d678 --- /dev/null +++ b/test/files/neg/stringinterpolation_macro-neg.scala @@ -0,0 +1,31 @@ +object Test extends App { + val s = "Scala" + val d = 8 + val b = false + val f = 3.14159 + + // 1) number of arguments + new StringContext().f() + new StringContext("", " is ", "%2d years old").f(s) + new StringContext("", " is ", "%2d years old").f(s, d, d) + new StringContext("", "").f() + + // 2) Interpolation mismatches + f"$s%b" + f"$s%c" + f"$f%c" + f"$s%x" + f"$b%d" + f"$s%d" + f"$f%o" + f"$s%e" + f"$b%f" + + { + implicit val strToInt1 = (s: String) => 1 + implicit val strToInt2 = (s: String) => 2 + f"$s%d" + } + + f"$s%i" +} diff --git a/test/files/run/stringinterpolation_macro-run.check b/test/files/run/stringinterpolation_macro-run.check new file mode 100644 index 0000000000..be62c5780b --- /dev/null +++ b/test/files/run/stringinterpolation_macro-run.check @@ -0,0 +1,62 @@ +false +false +true +false +true +FALSE +FALSE +TRUE +FALSE +TRUE +true +false +null +0 +80000000 +4c01926 +NULL +4C01926 +null +NULL +Scala +SCALA +5 +x +x +x +x +x +x +x +x +x +x +x +x +S +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +42 +3.400000e+00 +3.400000e+00 +3.400000e+00 +3.400000e+00 +3.400000e+00 +3.400000e+00 +3.000000e+00 +3.000000e+00 +05/26/12 +05/26/12 +05/26/12 +05/26/12 diff --git a/test/files/run/stringinterpolation_macro-run.scala b/test/files/run/stringinterpolation_macro-run.scala new file mode 100644 index 0000000000..9c59c334f8 --- /dev/null +++ b/test/files/run/stringinterpolation_macro-run.scala @@ -0,0 +1,103 @@ +object Test extends App { + +// 'b' / 'B' (category: general) +// ----------------------------- +println(f"${null}%b") +println(f"${false}%b") +println(f"${true}%b") +println(f"${new java.lang.Boolean(false)}%b") +println(f"${new java.lang.Boolean(true)}%b") + +println(f"${null}%B") +println(f"${false}%B") +println(f"${true}%B") +println(f"${new java.lang.Boolean(false)}%B") +println(f"${new java.lang.Boolean(true)}%B") + +implicit val stringToBoolean = java.lang.Boolean.parseBoolean(_: String) +println(f"${"true"}%b") +println(f"${"false"}%b") + +// 'h' | 'H' (category: general) +// ----------------------------- +println(f"${null}%h") +println(f"${0.0}%h") +println(f"${-0.0}%h") +println(f"${"Scala"}%h") + +println(f"${null}%H") +println(f"${"Scala"}%H") + +// 's' | 'S' (category: general) +// ----------------------------- +println(f"${null}%s") +println(f"${null}%S") +println(f"${"Scala"}%s") +println(f"${"Scala"}%S") +println(f"${5}") + +// 'c' | 'C' (category: character) +// ------------------------------- +println(f"${120:Char}%c") +println(f"${120:Byte}%c") +println(f"${120:Short}%c") +println(f"${120:Int}%c") +println(f"${new java.lang.Character('x')}%c") +println(f"${new java.lang.Byte(120:Byte)}%c") +println(f"${new java.lang.Short(120:Short)}%c") +println(f"${new java.lang.Integer(120)}%c") + +println(f"${'x' : java.lang.Character}%c") +println(f"${(120:Byte) : java.lang.Byte}%c") +println(f"${(120:Short) : java.lang.Short}%c") +println(f"${120 : java.lang.Integer}%c") + +implicit val stringToChar = (x: String) => x(0) +println(f"${"Scala"}%c") + +// 'd' | 'o' | 'x' | 'X' (category: integral) +// ------------------------------------------ +println(f"${120:Byte}%d") +println(f"${120:Short}%d") +println(f"${120:Int}%d") +println(f"${120:Long}%d") +println(f"${new java.lang.Byte(120:Byte)}%d") +println(f"${new java.lang.Short(120:Short)}%d") +println(f"${new java.lang.Integer(120)}%d") +println(f"${new java.lang.Long(120)}%d") +println(f"${120 : java.lang.Integer}%d") +println(f"${120 : java.lang.Long}%d") +println(f"${BigInt(120)}%d") +println(f"${new java.math.BigInteger("120")}%d") + +{ + implicit val strToShort = (s: String) => java.lang.Short.parseShort(s) + println(f"${"120"}%d") + implicit val strToInt = (s: String) => 42 + println(f"${"120"}%d") +} + +// 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' (category: floating point) +// ------------------------------------------------------------------ +println(f"${3.4f}%e") +println(f"${3.4}%e") +println(f"${3.4f : java.lang.Float}%e") +println(f"${3.4 : java.lang.Double}%e") +println(f"${BigDecimal(3.4)}%e") +println(f"${new java.math.BigDecimal(3.4)}%e") +println(f"${3}%e") +println(f"${3L}%e") + +// 't' | 'T' (category: date/time) +// ------------------------------- +import java.util.Calendar +import java.util.Locale +val c = Calendar.getInstance(Locale.US) +c.set(2012, Calendar.MAY, 26) +println(f"${c}%TD") +println(f"${c.getTime}%TD") +println(f"${c.getTime.getTime}%TD") + +implicit val strToDate = (x: String) => c +println(f"""${"1234"}%TD""") +} -- cgit v1.2.3 From b30533825853861cdf67f7c6b12d3e3d559fa8dc Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 6 Jul 2012 09:43:28 -0400 Subject: Fixed replacestarr to use packed libraries and also include source files. --- build.xml | 62 +++++++++++++++++++++++++------------------------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/build.xml b/build.xml index 33ebbfe377..2bb1dab5ac 100644 --- a/build.xml +++ b/build.xml @@ -2561,53 +2561,39 @@ STABLE REFERENCE (STARR) + +
    - - - - - - - - - - - - - - - - + + + + + + + + - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + @@ -2615,11 +2601,13 @@ STABLE REFERENCE (STARR) + + - +