From dac4e8f543a8e2e3bacf447d327fc8a7e99acb49 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 16 Jul 2012 23:12:35 +0200 Subject: SI-5784 Scaladoc: {Abstract,Alias} type templates Normally scaladoc won't generate template pages for anything other than packages, classes, traits and objects. But using the @template annotation on {abstract,alias} types, they get their own page and take part as full members in the diagrams. Furthermore, when looking for the companion object, if a value of type T is in scope, T will be taken as the companion object (even though it might be a class) All templates, including types are listed on the left navigation pane, so now adding @template to String can get scaladoc to generate (a no-comments) page for java.lang.String. The {abstract, alias} type icons need to be updated -- I just took the class icons and added a small x to them -- but they shoud be something else (maybe an underscore?)i TO USE THIS PATCH:
/** @contentDiagram */ // tells scaladoc to create a diagram of the
                       // templates contained in trait Base
trait Base {
  /** @template */ // tells scaladoc to create a page for Foo
  type T < Foo
  trait Foo { def foo: Int }
}

/** @contentDiagram */
trait Api extends Base {
  /** @template */
  override type T <: FooApi
  trait FooApi extends Foo { def bar: String }
}
--- src/compiler/scala/tools/ant/Scaladoc.scala | 2 +- src/compiler/scala/tools/nsc/doc/Settings.scala | 8 +- .../scala/tools/nsc/doc/html/HtmlFactory.scala | 7 +- .../scala/tools/nsc/doc/html/HtmlPage.scala | 4 +- .../tools/nsc/doc/html/page/ReferenceIndex.scala | 2 +- .../scala/tools/nsc/doc/html/page/Template.scala | 13 +- .../html/page/diagram/DotDiagramGenerator.scala | 12 ++ .../tools/nsc/doc/html/resource/lib/diagrams.css | 8 + .../scala/tools/nsc/doc/html/resource/lib/index.js | 48 +++--- .../doc/html/resource/lib/object_to_type_big.png | Bin 0 -> 9158 bytes .../scala/tools/nsc/doc/html/resource/lib/type.png | Bin 0 -> 3338 bytes .../tools/nsc/doc/html/resource/lib/type_big.png | Bin 0 -> 7691 bytes .../nsc/doc/html/resource/lib/type_diagram.png | Bin 0 -> 3895 bytes .../doc/html/resource/lib/type_to_object_big.png | Bin 0 -> 9054 bytes .../scala/tools/nsc/doc/model/Entity.scala | 10 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 181 +++++++++++++-------- .../nsc/doc/model/comment/CommentFactory.scala | 3 +- .../tools/nsc/doc/model/diagram/Diagram.scala | 4 +- .../nsc/doc/model/diagram/DiagramFactory.scala | 10 +- src/library-aux/scala/AnyRef.scala | 1 + .../scala/tools/partest/ScaladocModelTest.scala | 10 +- test/scaladoc/resources/SI-5784.scala | 28 ++++ test/scaladoc/resources/doc-root/AnyRef.scala | 1 + test/scaladoc/run/SI-5533.scala | 4 +- test/scaladoc/run/SI-5784.check | 1 + test/scaladoc/run/SI-5784.scala | 44 +++++ 26 files changed, 281 insertions(+), 120 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png create mode 100644 test/scaladoc/resources/SI-5784.scala create mode 100644 test/scaladoc/run/SI-5784.check create mode 100644 test/scaladoc/run/SI-5784.scala diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 61db3c7fa9..03cb770474 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -671,7 +671,7 @@ class Scaladoc extends ScalaMatchingTask { exception.printStackTrace() safeBuildError("Document failed because of an internal documenter error (" + exception.getMessage + "); see the error output for details.") - case exception => + case exception : Throwable => exception.printStackTrace() safeBuildError("Document failed because of an internal documenter error " + "(no error message provided); see the error output for details.") diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 7cb539feee..7662381186 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -183,6 +183,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "" ) + val docExpandAllTypes = BooleanSetting ( + "-expand-all-types", + "Expand all type aliases and abstract types into full template pages. (locally this can be done with the @template annotation)" + ) + // 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. @@ -195,7 +200,8 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docDiagramsDotTimeout, docDiagramsDotRestart, docImplicits, docImplicitsDebug, docImplicitsShowAll, docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, - docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages + docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages, + docExpandAllTypes ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala index 18cc65092b..436425df83 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -53,11 +53,16 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { "trait.png", "trait_big.png", "trait_diagram.png", + "type.png", + "type_big.png", + "type_diagram.png", "class_to_object_big.png", "object_to_class_big.png", - "object_to_trait_big.png", "trait_to_object_big.png", + "object_to_trait_big.png", + "type_to_object_big.png", + "object_to_type_big.png", "arrow-down.png", "arrow-right.png", diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index 226ed49aca..aa2df57967 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -211,10 +211,12 @@ abstract class HtmlPage extends Page { thisPage => else if (ety.isTrait) "trait_big.png" else if (ety.isClass && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "class_to_object_big.png" else if (ety.isClass) "class_big.png" + else if ((ety.isAbstractType || ety.isAliasType) && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "type_to_object_big.png" + else if ((ety.isAbstractType || ety.isAliasType)) "type_big.png" else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isClass) "object_to_class_big.png" else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isTrait) "object_to_trait_big.png" + else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && (ety.companion.get.isAbstractType || ety.companion.get.isAliasType)) "object_to_trait_big.png" else if (ety.isObject) "object_big.png" else if (ety.isPackage) "package_big.png" else "class_big.png" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not - } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala b/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala index a76cc231b4..effaee711d 100755 --- a/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala @@ -34,7 +34,7 @@ class ReferenceIndex(letter: Char, index: doc.Index, universe: Universe) extends } else { html } - }) + }).toList.distinct
{ 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 d691692920..487ef447e9 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -92,7 +92,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp

{ templatesToHtml(tpl.inTemplate.toRoot.reverse.tail, xml.Text(".")) }

} - +
{ tpl.companion match { @@ -734,20 +734,21 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp } }{ if (isReduced) NodeSeq.Empty else { mbr match { - 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) } - case abt: AbstractType => + case abt: MemberEntity with AbstractType => val b2s = boundsToHtml(abt.hi, abt.lo, hasLinks) if (b2s != NodeSeq.Empty) { b2s } else NodeSeq.Empty - case alt: AliasType => + case alt: MemberEntity with AliasType => = { typeToHtml(alt.alias, hasLinks) } + + case tpl: MemberTemplateEntity if !tpl.parentTypes.isEmpty => + extends { typeToHtml(tpl.parentTypes.map(_._2), hasLinks) } + case _ => NodeSeq.Empty } }} 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 f3454f71b8..bae61f1a3b 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 @@ -242,6 +242,8 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { attr ++= classStyle else if(node.isObjectNode) attr ++= objectStyle + else if(node.isTypeNode) + attr ++= typeStyle else attr ++= defaultStyle @@ -254,6 +256,8 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { img = "class_diagram.png" else if(node.isObjectNode) img = "object_diagram.png" + else if(node.isTypeNode) + img = "type_diagram.png" if(!img.equals("")) { img = "" @@ -307,6 +311,8 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { space + "trait" else if (node.isObjectNode) space + "object" + else if (node.isTypeNode) + space + "type" else default @@ -491,6 +497,12 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { "fontcolor" -> "#ffffff" ) + private val typeStyle = Map( + "color" -> "#115F3B", + "fillcolor" -> "#0A955B", + "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 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 index 04d29580b7..5fe33f72f5 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css @@ -119,6 +119,14 @@ svg.class-diagram .node.this.trait.over polygon fill: #235d7b; } +svg.package-diagram .node.type.over polygon, +svg.class-diagram .node.this.type.over polygon +{ + fill: #098552; + fill: #04663e; +} + + svg.package-diagram .node.object.over polygon { fill: #183377; diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js index eb7f752440..f29438edfb 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js @@ -15,15 +15,15 @@ var lastHash = ""; $(document).ready(function() { $('body').layout({ west__size: '20%' }); - $('#browser').layout({ + $('#browser').layout({ center__paneSelector: ".ui-west-center" //,center__initClosed:true ,north__paneSelector: ".ui-west-north" - }); + }); $('iframe').bind("load", function(){ var subtitle = $(this).contents().find('title').text(); $(document).attr('title', (title ? title + " - " : "") + subtitle); - + setUrlFragmentFromFrameSrc(); }); @@ -81,16 +81,16 @@ function setUrlFragmentFromFrameSrc() { var commonLength = location.pathname.lastIndexOf("/"); var frameLocation = frames["template"].location; var relativePath = frameLocation.pathname.slice(commonLength + 1); - + if(!relativePath || frameLocation.pathname.indexOf("/") < 0) return; - + // Add #, remove ".html" and replace "/" with "." fragment = "#" + relativePath.replace(/\.html$/, "").replace(/\//g, "."); - + // Add the frame's hash after an @ if(frameLocation.hash) fragment += ("@" + frameLocation.hash.slice(1)); - + // Use replace to not add history items lastFragment = fragment; location.replace(fragment); @@ -109,7 +109,7 @@ var Index = {}; if (type == 'object') { href = t['object']; } else { - href = t['class'] || t['trait'] || t['case class']; + href = t['class'] || t['trait'] || t['case class'] || t['type']; } return [ '' : ''; - inner += openLink(template, template['trait'] ? 'trait' : 'class'); + inner += openLink(template, template['trait'] ? 'trait' : template['type'] ? 'type' : 'class'); } else { inner += '
'; } @@ -245,6 +245,7 @@ function configureEntityList() { function prepareEntityList() { var classIcon = $("#library > img.class"); var traitIcon = $("#library > img.trait"); + var typeIcon = $("#library > img.type"); var objectIcon = $("#library > img.object"); var packageIcon = $("#library > img.package"); @@ -252,6 +253,7 @@ function prepareEntityList() { $('#tpl li.pack').each(function () { $("span.class", this).each(function() { $(this).replaceWith(classIcon.clone()); }); $("span.trait", this).each(function() { $(this).replaceWith(traitIcon.clone()); }); + $("span.type", this).each(function() { $(this).replaceWith(typeIcon.clone()); }); $("span.object", this).each(function() { $(this).replaceWith(objectIcon.clone()); }); $("span.package", this).each(function() { $(this).replaceWith(packageIcon.clone()); }); }); @@ -265,11 +267,11 @@ function keyboardScrolldownLeftPane() { scheduler.add("init", function() { $("#textfilter input").blur(); var $items = $("#tpl li"); - $items.first().addClass('selected'); + $items.first().addClass('selected'); $(window).bind("keydown", function(e) { var $old = $items.filter('.selected'), - $new; + $new; switch ( e.keyCode ) { @@ -286,7 +288,7 @@ function keyboardScrolldownLeftPane() { case 27: // escape $old.removeClass('selected'); $(window).unbind(e); - $("#textfilter input").focus(); + $("#textfilter input").focus(); break; @@ -296,7 +298,7 @@ function keyboardScrolldownLeftPane() { if (!$new.length) { $new = $old.parent().prev(); } - + if ($new.is('ol') && $new.children(':last').is('ol')) { $new = $new.children().children(':last'); } else if ($new.is('ol')) { @@ -313,17 +315,17 @@ function keyboardScrolldownLeftPane() { if ($new.is('ol')) { $new = $new.children(':first'); } - break; - } - + break; + } + if ($new.is('li')) { $old.removeClass('selected'); - $new.addClass('selected'); + $new.addClass('selected'); } else if (e.keyCode == 38) { $(window).unbind(e); $("#textfilter input").focus(); } - }); + }); }); } @@ -342,13 +344,13 @@ function configureTextFilter() { $("#template").contents().find("#mbrsel-input").focus(); input.attr("value", ""); return false; - } + } if (event.keyCode == 40) { // down arrow $(window).unbind("keydown"); keyboardScrolldownLeftPane(); return false; - } - textFilter(); + } + textFilter(); }); input.focus(function(event) { input.select(); }); }); @@ -419,7 +421,7 @@ function textFilter() { }); configureHideFilter(); }; - + scheduler.add('filter', searchLoop); } diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png new file mode 100644 index 0000000000..7502942eb6 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png new file mode 100644 index 0000000000..366ec4e992 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png new file mode 100644 index 0000000000..df0dc118bf Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png new file mode 100644 index 0000000000..d6fbb84ff2 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png new file mode 100644 index 0000000000..1bd2833a63 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png differ diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 620aa4253f..0836d7e4da 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -241,6 +241,11 @@ trait MemberTemplateEntity extends TemplateEntity with MemberEntity with HigherK /** The value parameters of this case class, or an empty list if this class is not a case class. As case class value * parameters cannot be curried, the outer list has exactly one element. */ def valueParams: List[List[ValueParam]] + + /** 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)] } /** A template (class, trait, object or package) for which documentation is available. Only templates for which @@ -259,11 +264,6 @@ trait DocTemplateEntity extends MemberTemplateEntity { * only if the `docsourceurl` setting has been set. */ def sourceUrl: Option[java.net.URL] - /** 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. */ def linearizationTemplates: List[TemplateEntity] diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index fc5fde3239..0ba32fceaa 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -265,6 +265,32 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override def isNoDocMemberTemplate = true lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) def valueParams: List[List[ValueParam]] = Nil /** TODO, these are now only computed for DocTemplates */ + + // Seems unused + // 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 = (this match { + case a: AliasType => sym.tpe.dealias.parents + case a: AbstractType => sym.info.bounds match { + case TypeBounds(lo, hi) => List(hi) + case _ => Nil + } + case _ => sym.tpe.parents + }) map { _.asSeenFrom(sym.thisType, sym) } + makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl) + } } /** The instantiation of `TemplateImpl` triggers the creation of the following entities: @@ -306,24 +332,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { else None } - 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) } - makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl) - } - protected def linearizationFromSymbol(symbol: Symbol): List[(TemplateEntity, TypeEntity)] = { symbol.ancestors map { ancestor => val typeEntity = makeType(symbol.info.baseType(ancestor), this) @@ -378,7 +386,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // 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(_, None, this))) + val ownMembers = (memberSymsEager.flatMap(makeMember(_, None, this))) // all the members that are documentented PLUS the members inherited by implicit conversions var members: List[MemberImpl] = ownMembers @@ -395,11 +403,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { */ def completeModel: Unit = { // DFS completion - for (member <- members) - member match { - case d: DocTemplateImpl => d.completeModel - case _ => - } + // since alias types and abstract types have no own members, there's no reason for them to call completeModel + if (!sym.isAliasType && !sym.isAbstractType) + for (member <- members) + member match { + case d: DocTemplateImpl => d.completeModel + case _ => + } members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, this)) @@ -432,15 +442,33 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override def isTemplate = true override def isDocTemplate = true - def companion = sym.companionSymbol match { - case NoSymbol => None - case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) => - makeTemplate(comSym) match { - case d: DocTemplateImpl => Some(d) - case _ => None + private[this] lazy val companionSymbol = + if (sym.isAliasType || sym.isAbstractType) { + inTpl.sym.info.member(sym.name.toTermName) match { + case NoSymbol => NoSymbol + case s => + s.info match { + case ot: OverloadedType => + NoSymbol + case _ => + // that's to navigate from val Foo: FooExtractor to FooExtractor :) + s.info.resultType.typeSymbol + } } - case _ => None - } + } + else + sym.companionSymbol + + def companion = + companionSymbol match { + case NoSymbol => None + case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) => + makeTemplate(comSym) match { + case d: DocTemplateImpl => Some(d) + case _ => None + } + case _ => None + } def constructors: List[MemberImpl with Constructor] = if (isClass) members collect { case d: Constructor => d } else Nil def primaryConstructor: Option[MemberImpl with Constructor] = if (isClass) constructors find { _.isPrimary } else None @@ -523,6 +551,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val name = optimize(sym.nameString) } + private trait AliasImpl { + def sym: Symbol + def inTpl: TemplateImpl + def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) + } + private trait TypeBoundsImpl { def sym: Symbol def inTpl: TemplateImpl @@ -591,13 +625,16 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def createRootPackage: PackageImpl = docTemplatesCache.get(RootPackage) match { case Some(root: PackageImpl) => root - case _ => modelCreation.createTemplate(RootPackage, null).asInstanceOf[PackageImpl] + case _ => modelCreation.createTemplate(RootPackage, null) match { + case Some(root: PackageImpl) => root + case _ => sys.error("Scaladoc: Unable to create root package!") + } } /** * Create a template, either a package, class, trait or object */ - def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { + def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): Option[MemberImpl] = { // don't call this after the model finished! assert(!modelFinished) @@ -616,11 +653,15 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { assert(!modelFinished) // only created BEFORE the model is finished - if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) + if (bSym.isAliasType && bSym != AnyRefClass) + new DocTemplateImpl(bSym, inTpl) with AliasImpl with AliasType { override def isAliasType = true } + else if (bSym.isAbstractType) + new DocTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType { override def isAbstractType = true } + else if (bSym.isModule) new DocTemplateImpl(bSym, inTpl) with Object {} - else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) + else if (bSym.isTrait) new DocTemplateImpl(bSym, inTpl) with Trait {} - else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass)) + else if (bSym.isClass || bSym == AnyRefClass) new DocTemplateImpl(bSym, inTpl) with Class {} else sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a documentable template.") @@ -628,7 +669,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val bSym = normalizeTemplate(aSym) if (docTemplatesCache isDefinedAt bSym) - return docTemplatesCache(bSym) + return Some(docTemplatesCache(bSym)) /* Three cases of templates: * (1) root package -- special cased for bootstrapping @@ -636,7 +677,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { * (3) class/object/trait */ if (bSym == RootPackage) // (1) - new RootPackageImpl(bSym) { + Some(new RootPackageImpl(bSym) { override lazy val comment = createRootPackageComment override val name = "root" override def inTemplate = this @@ -648,21 +689,28 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { (bSym.info.members ++ EmptyPackage.info.members) filter { s => s != EmptyPackage && s != RootPackage } - } + }) else if (bSym.isPackage) // (2) - inTpl match { - case inPkg: PackageImpl => - val pack = new PackageImpl(bSym, inPkg) {} - if (pack.templates.filter(_.isDocTemplate).isEmpty && pack.memberSymsLazy.isEmpty) - droppedPackages += pack - pack - case _ => - sys.error("'" + bSym + "' must be in a package") - } + if (settings.skipPackage(makeQualifiedName(bSym))) + None + else + inTpl match { + case inPkg: PackageImpl => + val pack = new PackageImpl(bSym, inPkg) {} + // Used to check package pruning works: + //println(pack.qualifiedName) + if (pack.templates.filter(_.isDocTemplate).isEmpty && pack.memberSymsLazy.isEmpty) { + droppedPackages += pack + None + } else + Some(pack) + case _ => + sys.error("'" + bSym + "' must be in a package") + } else { // no class inheritance at this point - assert(inOriginalOwner(bSym, inTpl)) - createDocTemplate(bSym, inTpl) + assert(inOriginalOwner(bSym, inTpl) || bSym.isAbstractType || bSym.isAliasType, bSym + " in " + inTpl) + Some(createDocTemplate(bSym, inTpl)) } } @@ -750,28 +798,16 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val { override def isVal = true }) - else if (bSym.isAbstractType) + else if (bSym.isAbstractType && !typeShouldDocument(bSym, inTpl)) Some(new MemberTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType { override def isAbstractType = true }) - else if (bSym.isAliasType && bSym != AnyRefClass) - Some(new MemberTemplateImpl(bSym, inTpl) with AliasType { + else if (bSym.isAliasType && !typeShouldDocument(bSym, inTpl)) + Some(new MemberTemplateImpl(bSym, inTpl) with AliasImpl with AliasType { override def isAliasType = true - def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) }) - else if (bSym.isPackage && !modelFinished) - if (settings.skipPackage(makeQualifiedName(bSym))) None else - 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) && inOriginalOwner(bSym, inTpl)) - Some(modelCreation.createTemplate(bSym, inTpl)) + else if (!modelFinished && (bSym.isPackage || bSym.isAliasType || bSym.isAbstractType || templateShouldDocument(bSym, inTpl))) + modelCreation.createTemplate(bSym, inTpl) else None } @@ -915,7 +951,7 @@ 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, tpl: Option[DocTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { + def makeParentTypes(aType: Type, tpl: Option[MemberTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { case RefinedType(parents, defs) => val ignoreParents = Set[Symbol](AnyClass, AnyRefClass, ObjectClass) val filtParents = @@ -978,7 +1014,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { normalizeTemplate(aSym.owner) == normalizeTemplate(inTpl.sym) def templateShouldDocument(aSym: Symbol, inTpl: TemplateImpl): Boolean = - (aSym.isClass || aSym.isModule || aSym == AnyRefClass) && + (aSym.isTrait || aSym.isClass || aSym.isModule) && localShouldDocument(aSym) && !isEmptyJavaObject(aSym) && // either it's inside the original owner or we can document it later: @@ -1014,5 +1050,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // 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) + + // whether or not to create a page for an {abstract,alias} type + def typeShouldDocument(bSym: Symbol, inTpl: DocTemplateImpl) = + (settings.docExpandAllTypes.value && (bSym.sourceFile != null)) || + global.expandedDocComment(bSym, inTpl.sym).contains("@template") } 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 df913555a7..a57ccd36c2 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -350,7 +350,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member case None => List.empty } - val tagsWithoutDiagram = tags.filterNot(pair => pair._1 == inheritDiagramTag || pair._1 == contentDiagramTag) + val stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template")) + val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) val bodyTags: mutable.Map[TagKey, List[Body]] = mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos, inTplOpt))} toSeq: _*) 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 902d5da240..c2aa1f17f3 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -70,7 +70,8 @@ abstract class Node { 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 isTypeNode = if (doctpl.isDefined) doctpl.get.isAbstractType || doctpl.get.isAliasType else false + def isOtherNode = !(isClassNode || isTraitNode || isObjectNode || isTypeNode) def isImplicitNode = false def isOutsideNode = false def tooltip: Option[String] @@ -91,6 +92,7 @@ abstract class 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 TypeNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTypeNode) 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 } 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 e0f6d42a68..2645d8fd14 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -121,12 +121,10 @@ trait DiagramFactory extends DiagramDirectiveParser { // for each node, add its subclasses for (node <- nodesAll if !classExcluded(node)) { node match { - case dnode: DocTemplateImpl => - var superClasses = dnode.parentTypes.map(_._1) + case dnode: MemberTemplateImpl => + var superClasses = dnode.parentTypes.map(_._1).filter(nodesAll.contains(_)) - superClasses = superClasses.filter(nodesAll.contains(_)) - - // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. + // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to add nodes to diagrams. if (pack.sym == ScalaPackage) if (dnode.sym == NullClass) superClasses = List(makeTemplate(AnyRefClass)) @@ -142,7 +140,7 @@ trait DiagramFactory extends DiagramDirectiveParser { } mapNodes += node -> ( - if (node.inTemplate == pack && !node.isNoDocMemberTemplate) + if (node.inTemplate == pack && (node.isDocTemplate || node.isAbstractType || node.isAliasType)) NormalNode(node.resultType, Some(node))() else OutsideNode(node.resultType, Some(node))() diff --git a/src/library-aux/scala/AnyRef.scala b/src/library-aux/scala/AnyRef.scala index 1eefb0c806..7d8b9f9e76 100644 --- a/src/library-aux/scala/AnyRef.scala +++ b/src/library-aux/scala/AnyRef.scala @@ -10,6 +10,7 @@ package scala /** Class `AnyRef` is the root class of all ''reference types''. * All types except the value types descend from this class. + * @template */ trait AnyRef extends Any { diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index d8d5dfbbeb..adf7abe11c 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -130,11 +130,17 @@ abstract class ScaladocModelTest extends DirectTest { def _conversion(name: String): ImplicitConversion = getTheFirst(_conversions(name), tpl.qualifiedName + ".conversion(" + name + ")") def _conversions(name: String): List[ImplicitConversion] = tpl.conversions.filter(_.conversionQualifiedName == name) - def _absType(name: String): MemberEntity = getTheFirst(_methods(name), tpl.qualifiedName + ".abstractType(" + name + ")") + def _absType(name: String): MemberEntity = getTheFirst(_absTypes(name), tpl.qualifiedName + ".abstractType(" + name + ")") def _absTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAbstractType) - def _aliasType(name: String): MemberEntity = getTheFirst(_methods(name), tpl.qualifiedName + ".aliasType(" + name + ")") + def _absTypeTpl(name: String): DocTemplateEntity = getTheFirst(_absTypeTpls(name), tpl.qualifiedName + ".abstractType(" + name + ")") + def _absTypeTpls(name: String): List[DocTemplateEntity] = tpl.members.collect({ case dtpl: DocTemplateEntity with AbstractType if dtpl.name == name => dtpl }) + + def _aliasType(name: String): MemberEntity = getTheFirst(_aliasTypes(name), tpl.qualifiedName + ".aliasType(" + name + ")") def _aliasTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAliasType) + + def _aliasTypeTpl(name: String): DocTemplateEntity = getTheFirst(_aliasTypeTpls(name), tpl.qualifiedName + ".aliasType(" + name + ")") + def _aliasTypeTpls(name: String): List[DocTemplateEntity] = tpl.members.collect({ case dtpl: DocTemplateEntity with AliasType if dtpl.name == name => dtpl }) } class PackageAccess(pack: Package) extends TemplateAccess(pack) { diff --git a/test/scaladoc/resources/SI-5784.scala b/test/scaladoc/resources/SI-5784.scala new file mode 100644 index 0000000000..175cc3cf33 --- /dev/null +++ b/test/scaladoc/resources/SI-5784.scala @@ -0,0 +1,28 @@ +package test.templates { + object `package` { + /** @template */ + type String = java.lang.String + val String = new StringCompanion + class StringCompanion { def boo = ??? } + } + + /** @contentDiagram */ + trait Base { + /** @template */ + type String = test.templates.String + /** @template + * @inheritanceDiagram */ + type T <: Foo + val T: FooExtractor + trait Foo { def foo: Int } + trait FooExtractor { def apply(foo: Int); def unapply(t: Foo): Option[Int] } + } + + /** @contentDiagram */ + trait Api extends Base { + /** @template + * @inheritanceDiagram */ + override type T <: FooApi + trait FooApi extends Foo { def bar: String } + } +} diff --git a/test/scaladoc/resources/doc-root/AnyRef.scala b/test/scaladoc/resources/doc-root/AnyRef.scala index 1eefb0c806..7d8b9f9e76 100644 --- a/test/scaladoc/resources/doc-root/AnyRef.scala +++ b/test/scaladoc/resources/doc-root/AnyRef.scala @@ -10,6 +10,7 @@ package scala /** Class `AnyRef` is the root class of all ''reference types''. * All types except the value types descend from this class. + * @template */ trait AnyRef extends Any { diff --git a/test/scaladoc/run/SI-5533.scala b/test/scaladoc/run/SI-5533.scala index e7b5f57860..989d9aa13a 100644 --- a/test/scaladoc/run/SI-5533.scala +++ b/test/scaladoc/run/SI-5533.scala @@ -32,6 +32,8 @@ object Test extends ScaladocModelTest { val B = b._class("B") val D = b._class("D") testDiagram(B, B.contentDiagram, 2, 1) - testDiagram(D, D.contentDiagram, 2, 1) + // unfortunately not all packages, as B1 extends A.this.A1 and it gets the wrong member -- maybe we should model + // things as we do for symbols? + testDiagram(D, D.contentDiagram, 3, 2) } } \ No newline at end of file diff --git a/test/scaladoc/run/SI-5784.check b/test/scaladoc/run/SI-5784.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-5784.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-5784.scala b/test/scaladoc/run/SI-5784.scala new file mode 100644 index 0000000000..318eb78b2a --- /dev/null +++ b/test/scaladoc/run/SI-5784.scala @@ -0,0 +1,44 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def resourceFile: String = "SI-5784.scala" + + // no need for special settings + 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._ + + val main = rootPackage._package("test")._package("templates") + + val String = main._aliasTypeTpl("String") + assert(String.companion.isDefined, "test.templates.String should have a pseudo-companion object") + + val Base = main._trait("Base") + assert(Base.members.filter(_.inDefinitionTemplates.head == Base).length == 5, Base.members.filter(_.inDefinitionTemplates.head == Base).length + " == 5") + assert(Base.members.collect{case d: DocTemplateEntity => d}.length == 4, Base.members.collect{case d: DocTemplateEntity => d}.length == 4) + testDiagram(Base, Base.contentDiagram, 2, 1) + + val BaseT = Base._absTypeTpl("T") + val Foo = Base._trait("Foo") + assert(BaseT.members.filter(_.inDefinitionTemplates.head == Base).length == 0, BaseT.members.filter(_.inDefinitionTemplates.head == Base).length + " == 0") + assert(BaseT.members.map(_.name).sorted == Foo.members.map(_.name).sorted, BaseT.members.map(_.name).sorted + " == " + Foo.members.map(_.name).sorted) + assert(BaseT.companion.isDefined, "test.templates.Base.T should have a pseudo-companion object") + testDiagram(BaseT, BaseT.inheritanceDiagram, 2, 1) + + val Api = main._trait("Api") + assert(Api.members.filter(_.inDefinitionTemplates.head == Api).length == 2, Api.members.filter(_.inDefinitionTemplates.head == Api).length + " == 2") // FooApi and override type T + assert(Api.members.collect{case d: DocTemplateEntity => d}.length == 5, Api.members.collect{case d: DocTemplateEntity => d}.length == 5) + testDiagram(Api, Api.contentDiagram, 3, 2) + + val ApiT = Api._absTypeTpl("T") + val FooApi = Api._trait("FooApi") + assert(ApiT.members.filter(_.inDefinitionTemplates.head == Api).length == 0, ApiT.members.filter(_.inDefinitionTemplates.head == Api).length + " == 0") + assert(ApiT.members.map(_.name).sorted == FooApi.members.map(_.name).sorted, ApiT.members.map(_.name).sorted + " == " + FooApi.members.map(_.name).sorted) + assert(ApiT.companion.isDefined, "test.templates.Api.T should have a pseudo-companion object") + testDiagram(ApiT, ApiT.inheritanceDiagram, 2, 1) + } +} \ No newline at end of file -- cgit v1.2.3