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. --- test/scaladoc/run/SI-5373.scala | 4 ++-- test/scaladoc/run/package-object.check | 3 ++- test/scaladoc/run/package-object.scala | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'test') 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 (limited to 'test') 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)
  1. + 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 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 (limited to 'test') 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
  2. Hide All
  3. 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 (limited to 'test') 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 (limited to 'test') 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 (limited to 'test') 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 (limited to 'test') 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 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(-) (limited to 'test') 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 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(-) (limited to 'test') 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 (limited to 'test') 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 a3fe989833cc482dbae04a2e2f822a74cad67a36 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Fri, 6 Jul 2012 21:13:44 +0400 Subject: SI-6036 yet again makes sense of magic symbols --- .../scala/reflect/makro/runtime/Mirrors.scala | 9 ++- .../scala/tools/nsc/typechecker/Typers.scala | 4 +- .../scala/reflect/internal/BuildUtils.scala | 2 +- .../scala/reflect/internal/Definitions.scala | 69 +++++++++++----------- src/reflect/scala/reflect/internal/Mirrors.scala | 4 +- .../reflect/internal/pickling/UnPickler.scala | 6 +- .../scala/reflect/runtime/JavaMirrors.scala | 21 ++++--- test/files/run/reflection-magicsymbols.check | 22 +++++++ test/files/run/reflection-magicsymbols.scala | 11 ++++ 9 files changed, 92 insertions(+), 56 deletions(-) create mode 100644 test/files/run/reflection-magicsymbols.check create mode 100644 test/files/run/reflection-magicsymbols.scala (limited to 'test') diff --git a/src/compiler/scala/reflect/makro/runtime/Mirrors.scala b/src/compiler/scala/reflect/makro/runtime/Mirrors.scala index 79fa07fdb4..ec970ee696 100644 --- a/src/compiler/scala/reflect/makro/runtime/Mirrors.scala +++ b/src/compiler/scala/reflect/makro/runtime/Mirrors.scala @@ -24,11 +24,14 @@ trait Mirrors { else NoSymbol } + private lazy val libraryClasspathLoader: ClassLoader = { + val classpath = platform.classPath.asURLs + ScalaClassLoader.fromURLs(classpath) + } + private def isJavaClass(path: String): Boolean = try { - val classpath = platform.classPath.asURLs - var classLoader = ScalaClassLoader.fromURLs(classpath) - Class.forName(path, true, classLoader) + Class.forName(path, true, libraryClasspathLoader) true } catch { case (_: ClassNotFoundException) | (_: NoClassDefFoundError) | (_: IncompatibleClassChangeError) => diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 018daf4568..42672c1d3b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4431,7 +4431,7 @@ trait Typers extends Modes with Adaptations with Tags { if (!qual.tpe.widen.isErroneous) { if ((mode & QUALmode) != 0) { - val lastTry = missingHook(qual.tpe.typeSymbol, name) + val lastTry = rootMirror.missingHook(qual.tpe.typeSymbol, name) if (lastTry != NoSymbol) return typed1(tree setSymbol lastTry, mode, pt) } NotAMemberError(tree, qual, name) @@ -4650,7 +4650,7 @@ trait Typers extends Modes with Adaptations with Tags { log("Allowing empty package member " + name + " due to settings.") else { if ((mode & QUALmode) != 0) { - val lastTry = missingHook(rootMirror.RootClass, name) + val lastTry = rootMirror.missingHook(rootMirror.RootClass, name) if (lastTry != NoSymbol) return typed1(tree setSymbol lastTry, mode, pt) } if (settings.debug.value) { diff --git a/src/reflect/scala/reflect/internal/BuildUtils.scala b/src/reflect/scala/reflect/internal/BuildUtils.scala index 0bf5aa4b69..ad59605760 100644 --- a/src/reflect/scala/reflect/internal/BuildUtils.scala +++ b/src/reflect/scala/reflect/internal/BuildUtils.scala @@ -20,7 +20,7 @@ trait BuildUtils extends base.BuildUtils { self: SymbolTable => val result = owner.info decl name if (result ne NoSymbol) result else - mirrorThatLoaded(owner).tryMissingHooks(owner, name) orElse + mirrorThatLoaded(owner).missingHook(owner, name) orElse MissingRequirementError.notFound("%s %s in %s".format(if (name.isTermName) "term" else "type", name, owner.fullName)) } diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7891433b4f..0b592be454 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1125,6 +1125,39 @@ trait Definitions extends api.StandardDefinitions { /** Is symbol a phantom class for which no runtime representation exists? */ lazy val isPhantomClass = Set[Symbol](AnyClass, AnyValClass, NullClass, NothingClass) + lazy val magicSymbols = List( + AnnotationDefaultAttr, // #2264 + RepeatedParamClass, + JavaRepeatedParamClass, + ByNameParamClass, + AnyClass, + AnyRefClass, + AnyValClass, + NullClass, + NothingClass, + SingletonClass, + EqualsPatternClass, + Any_==, + Any_!=, + Any_equals, + Any_hashCode, + Any_toString, + Any_getClass, + Any_isInstanceOf, + Any_asInstanceOf, + Any_##, + Object_eq, + Object_ne, + Object_==, + Object_!=, + Object_##, + Object_synchronized, + Object_isInstanceOf, + Object_asInstanceOf, + String_+, + ComparableClass, + JavaSerializableClass + ) /** Is the symbol that of a parent which is added during parsing? */ lazy val isPossibleSyntheticParent = ProductClass.toSet[Symbol] + ProductRootClass + SerializableClass @@ -1188,41 +1221,7 @@ trait Definitions extends api.StandardDefinitions { def init() { if (isInitialized) return - - val forced = List( // force initialization of every symbol that is entered as a side effect - AnnotationDefaultAttr, // #2264 - RepeatedParamClass, - JavaRepeatedParamClass, - ByNameParamClass, - AnyClass, - AnyRefClass, - AnyValClass, - NullClass, - NothingClass, - SingletonClass, - EqualsPatternClass, - Any_==, - Any_!=, - Any_equals, - Any_hashCode, - Any_toString, - Any_getClass, - Any_isInstanceOf, - Any_asInstanceOf, - Any_##, - Object_eq, - Object_ne, - Object_==, - Object_!=, - Object_##, - Object_synchronized, - Object_isInstanceOf, - Object_asInstanceOf, - String_+, - ComparableClass, - JavaSerializableClass - ) - + val forced = magicSymbols // force initialization of every symbol that is entered as a side effect isInitialized = true } //init diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index dedfd41e83..210af661ee 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -41,7 +41,7 @@ trait Mirrors extends api.Mirrors { if (result != NoSymbol) result else { if (settings.debug.value) { log(sym.info); log(sym.info.members) }//debug - tryMissingHooks(owner, name) orElse { + thisMirror.missingHook(owner, name) orElse { MissingRequirementError.notFound((if (path.isTermName) "object " else "class ")+path+" in "+thisMirror) } } @@ -51,7 +51,7 @@ trait Mirrors extends api.Mirrors { protected def symbolTableMissingHook(owner: Symbol, name: Name): Symbol = self.missingHook(owner, name) - private[reflect] def tryMissingHooks(owner: Symbol, name: Name): Symbol = mirrorMissingHook(owner, name) orElse symbolTableMissingHook(owner, name) + private[scala] def missingHook(owner: Symbol, name: Name): Symbol = mirrorMissingHook(owner, name) orElse symbolTableMissingHook(owner, name) /** If you're looking for a class, pass a type name. * If a module, a term name. diff --git a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala index 757163a074..4d8e932862 100644 --- a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala +++ b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala @@ -818,7 +818,7 @@ abstract class UnPickler /*extends reflect.generic.UnPickler*/ { throw new RuntimeException("malformed Scala signature of " + classRoot.name + " at " + readIndex + "; " + msg) protected def errorMissingRequirement(name: Name, owner: Symbol): Symbol = - missingHook(owner, name) orElse MissingRequirementError.signal( + mirrorThatLoaded(owner).missingHook(owner, name) orElse MissingRequirementError.signal( s"bad reference while unpickling $filename: ${name.longString} not found in ${owner.tpe.widen}" ) @@ -832,8 +832,10 @@ abstract class UnPickler /*extends reflect.generic.UnPickler*/ { * Similar in intent to what SymbolLoader does (but here we don't have access to * error reporting, so we rely on the typechecker to report the error). */ - def toTypeError(e: MissingRequirementError) = + def toTypeError(e: MissingRequirementError) = { + // e.printStackTrace() new TypeError(e.msg) + } /** A lazy type which when completed returns type at index `i`. */ private class LazyTypeRef(i: Int) extends LazyType { diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 41955170bd..eae6a3b297 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -999,10 +999,10 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym mirrors(rootToLoader getOrElseUpdate(root, findLoader)).get.get } - private def byName(sym: Symbol): (Name, Symbol) = sym.name -> sym - - private lazy val phantomTypes: Map[Name, Symbol] = - Map(byName(definitions.AnyRefClass)) ++ (definitions.isPhantomClass map byName) + private lazy val magicSymbols: Map[(String, Name), Symbol] = { + def mapEntry(sym: Symbol): ((String, Name), Symbol) = (sym.owner.fullName, sym.name) -> sym + Map() ++ (definitions.magicSymbols filter (_.isClass) map mapEntry) + } /** 1. If `owner` is a package class (but not the empty package) and `name` is a term name, make a new package * ., otherwise return NoSymbol. @@ -1020,13 +1020,12 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym if (name.isTermName && !owner.isEmptyPackageClass) return mirror.makeScalaPackage( if (owner.isRootSymbol) name.toString else owner.fullName+"."+name) - if (owner.name.toTermName == nme.scala_ && owner.owner.isRoot) - phantomTypes get name match { - case Some(tsym) => - owner.info.decls enter tsym - return tsym - case None => - } + magicSymbols get (owner.fullName, name) match { + case Some(tsym) => + owner.info.decls enter tsym + return tsym + case None => + } } info("*** missing: "+name+"/"+name.isTermName+"/"+owner+"/"+owner.hasPackageFlag+"/"+owner.info.decls.getClass) super.missingHook(owner, name) diff --git a/test/files/run/reflection-magicsymbols.check b/test/files/run/reflection-magicsymbols.check new file mode 100644 index 0000000000..2600847d99 --- /dev/null +++ b/test/files/run/reflection-magicsymbols.check @@ -0,0 +1,22 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> import scala.reflect.runtime.universe._ +import scala.reflect.runtime.universe._ + +scala> class A { def foo(x: Int*) = 1 } +defined class A + +scala> val sig = typeOf[A] member newTermName("foo") typeSignature +warning: there were 1 feature warnings; re-run with -feature for details +sig: reflect.runtime.universe.Type = (x: )scala.Int + +scala> val x = sig.asInstanceOf[MethodType].params.head +x: reflect.runtime.universe.Symbol = value x + +scala> println(x.typeSignature) +scala.Int* + +scala> diff --git a/test/files/run/reflection-magicsymbols.scala b/test/files/run/reflection-magicsymbols.scala new file mode 100644 index 0000000000..a40845d6ac --- /dev/null +++ b/test/files/run/reflection-magicsymbols.scala @@ -0,0 +1,11 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ + |import scala.reflect.runtime.universe._ + |class A { def foo(x: Int*) = 1 } + |val sig = typeOf[A] member newTermName("foo") typeSignature + |val x = sig.asInstanceOf[MethodType].params.head + |println(x.typeSignature) + |""".stripMargin +} -- cgit v1.2.3 From f2dbe673756302d5f5824fd8d0e6e3b3eb45a57b Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Fri, 6 Jul 2012 19:46:14 -0700 Subject: Tweak test to pass under java 7. --- test/files/run/t3613.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/files/run/t3613.scala b/test/files/run/t3613.scala index c3b249571b..171a6a21aa 100644 --- a/test/files/run/t3613.scala +++ b/test/files/run/t3613.scala @@ -8,7 +8,7 @@ class Boopy { case "Boopy" => fireIntervalAdded( model, 0, 1 ) } def getSize = 0 - def getElementAt( idx: Int ) : AnyRef = "egal" + def getElementAt( idx: Int ) = ??? } } -- cgit v1.2.3 From da587e31782ecbad27bd9d9247100b3ac1827b11 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 8 Jul 2012 14:51:12 +0200 Subject: SI-6042 Improve type selection from volatile type error - Display the type of the typed qualifier (qual1), to avoid the message "Illegal type selection from volatile type null". - Show the upper bound, which is used to calculate the volatility. --- src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala | 4 +++- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- test/files/neg/t6042.check | 4 ++++ test/files/neg/t6042.scala | 8 ++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 test/files/neg/t6042.check create mode 100644 test/files/neg/t6042.scala (limited to 'test') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 49f5fca19d..ba6c43f9d3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -562,7 +562,9 @@ trait ContextErrors { // SelectFromTypeTree def TypeSelectionFromVolatileTypeError(tree: Tree, qual: Tree) = { - issueNormalTypeError(tree, "illegal type selection from volatile type "+qual.tpe) + val hiBound = qual.tpe.bounds.hi + val addendum = if (hiBound =:= qual.tpe) "" else s" (with upper bound ${hiBound})" + issueNormalTypeError(tree, s"illegal type selection from volatile type ${qual.tpe}${addendum}") setError(tree) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index ccd346e72d..20241953c1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5114,7 +5114,7 @@ trait Typers extends Modes with Adaptations with Tags { case SelectFromTypeTree(qual, selector) => val qual1 = typedType(qual, mode) - if (qual1.tpe.isVolatile) TypeSelectionFromVolatileTypeError(tree, qual) + if (qual1.tpe.isVolatile) TypeSelectionFromVolatileTypeError(tree, qual1) else typedSelect(qual1, selector) case CompoundTypeTree(templ) => diff --git a/test/files/neg/t6042.check b/test/files/neg/t6042.check new file mode 100644 index 0000000000..221f06e2c5 --- /dev/null +++ b/test/files/neg/t6042.check @@ -0,0 +1,4 @@ +t6042.scala:7: error: illegal type selection from volatile type a.OpSemExp (with upper bound LazyExp[a.OpSemExp] with _$1) + def foo[AA <: LazyExp[_]](a: AA): a.OpSemExp#Val = ??? // a.OpSemExp is volatile, because of `with This` + ^ +one error found diff --git a/test/files/neg/t6042.scala b/test/files/neg/t6042.scala new file mode 100644 index 0000000000..5a123d17ca --- /dev/null +++ b/test/files/neg/t6042.scala @@ -0,0 +1,8 @@ +trait LazyExp[+This <: LazyExp[This]] { this: This => + type OpSemExp <: LazyExp[OpSemExp] with This + type Val +} + +object Test { + def foo[AA <: LazyExp[_]](a: AA): a.OpSemExp#Val = ??? // a.OpSemExp is volatile, because of `with This` +} -- cgit v1.2.3 From 713ce7c5bb01ecf10633193baa66aa7161c68be7 Mon Sep 17 00:00:00 2001 From: clhodapp Date: Fri, 6 Jul 2012 07:04:50 -0500 Subject: New logic for TermSymbol.resolveOverloaded --- src/reflect/scala/reflect/api/Symbols.scala | 8 +- src/reflect/scala/reflect/internal/Symbols.scala | 323 +++++++++++++++++---- test/files/run/reflect-overload.scala | 19 -- .../run/reflect-resolveoverload-bynameparam.scala | 32 ++ .../run/reflect-resolveoverload-expected.scala | 43 +++ .../run/reflect-resolveoverload-invalid.scala | 43 +++ test/files/run/reflect-resolveoverload-named.scala | 26 ++ test/files/run/reflect-resolveoverload-targs.scala | 29 ++ .../reflect-resolveoverload-tparm-substitute.scala | 77 +++++ .../run/reflect-resolveoverload-variadic.scala | 27 ++ test/files/run/reflect-resolveoverload1.scala | 19 ++ test/files/run/reflect-resolveoverload2.scala | 40 +++ 12 files changed, 609 insertions(+), 77 deletions(-) delete mode 100644 test/files/run/reflect-overload.scala create mode 100644 test/files/run/reflect-resolveoverload-bynameparam.scala create mode 100644 test/files/run/reflect-resolveoverload-expected.scala create mode 100644 test/files/run/reflect-resolveoverload-invalid.scala create mode 100644 test/files/run/reflect-resolveoverload-named.scala create mode 100644 test/files/run/reflect-resolveoverload-targs.scala create mode 100644 test/files/run/reflect-resolveoverload-tparm-substitute.scala create mode 100644 test/files/run/reflect-resolveoverload-variadic.scala create mode 100644 test/files/run/reflect-resolveoverload1.scala create mode 100644 test/files/run/reflect-resolveoverload2.scala (limited to 'test') diff --git a/src/reflect/scala/reflect/api/Symbols.scala b/src/reflect/scala/reflect/api/Symbols.scala index eb9921a31a..11c7a38498 100644 --- a/src/reflect/scala/reflect/api/Symbols.scala +++ b/src/reflect/scala/reflect/api/Symbols.scala @@ -232,7 +232,13 @@ trait Symbols extends base.Symbols { self: Universe => /** The overloaded alternatives of this symbol */ def alternatives: List[Symbol] - def resolveOverloaded(pre: Type = NoPrefix, targs: Seq[Type] = List(), actuals: Seq[Type]): Symbol + def resolveOverloaded( + pre: Type = NoPrefix, + targs: Seq[Type] = List(), + posVargs: Seq[Type] = List(), + nameVargs: Seq[(TermName, Type)] = List(), + expected: Type = NoType + ): Symbol } /** The API of type symbols */ diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 119c3d42fd..3cb9f6ff37 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -82,71 +82,280 @@ trait Symbols extends api.Symbols { self: SymbolTable => def getAnnotations: List[AnnotationInfo] = { initialize; annotations } def setAnnotations(annots: AnnotationInfo*): this.type = { setAnnotations(annots.toList); this } - private def lastElemType(ts: Seq[Type]): Type = ts.last.normalize.typeArgs.head + def resolveOverloaded( + pre: Type, + targs: Seq[Type], + posVargTypes: Seq[Type], + nameVargTypes: Seq[(TermName, Type)], + expected: Type + ): Symbol = { + + // Begin Correlation Helpers + + def isCompatible(tp: Type, pt: Type): Boolean = { + def isCompatibleByName(tp: Type, pt: Type): Boolean = pt match { + case TypeRef(_, ByNameParamClass, List(res)) if !definitions.isByNameParamType(tp) => + isCompatible(tp, res) + case _ => + false + } + (tp <:< pt) || isCompatibleByName(tp, pt) + } - private def formalTypes(formals: List[Type], nargs: Int): List[Type] = { - val formals1 = formals mapConserve { - case TypeRef(_, ByNameParamClass, List(arg)) => arg - case formal => formal + def signatureAsSpecific(method1: MethodSymbol, method2: MethodSymbol): Boolean = { + (substituteTypeParams(method1), substituteTypeParams(method2)) match { + case (NullaryMethodType(r1), NullaryMethodType(r2)) => + r1 <:< r2 + case (NullaryMethodType(_), MethodType(_, _)) => + true + case (MethodType(_, _), NullaryMethodType(_)) => + false + case (MethodType(p1, _), MethodType(p2, _)) => + val len = p1.length max p2.length + val sub = extend(p1 map (_.typeSignature), len) + val sup = extend(p2 map (_.typeSignature), len) + (sub corresponds sup)(isCompatible) + } } - if (isVarArgTypes(formals1)) { - val ft = lastElemType(formals) - formals1.init ::: List.fill(nargs - (formals1.length - 1))(ft) - } else formals1 - } - - def resolveOverloaded(pre: Type, targs: Seq[Type], actuals: Seq[Type]): Symbol = { - def firstParams(tpe: Type): (List[Symbol], List[Type]) = tpe match { - case PolyType(tparams, restpe) => - val (Nil, formals) = firstParams(restpe) - (tparams, formals) - case MethodType(params, _) => - (Nil, params map (_.tpe)) - case _ => - (Nil, Nil) + + def scopeMoreSpecific(method1: MethodSymbol, method2: MethodSymbol): Boolean = { + val o1 = method1.owner.asClassSymbol + val o2 = method2.owner.asClassSymbol + val c1 = if (o1.hasFlag(Flag.MODULE)) o1.companionSymbol else o1 + val c2 = if (o2.hasFlag(Flag.MODULE)) o2.companionSymbol else o2 + c1.typeSignature <:< c2.typeSignature } - def isApplicable(alt: Symbol, targs: List[Type], actuals: Seq[Type]) = { - def isApplicableType(tparams: List[Symbol], tpe: Type): Boolean = { - val (tparams, formals) = firstParams(pre memberType alt) - val formals1 = formalTypes(formals, actuals.length) - val actuals1 = - if (isVarArgTypes(actuals)) { - if (!isVarArgTypes(formals)) return false - actuals.init :+ lastElemType(actuals) - } else actuals - if (formals1.length != actuals1.length) return false - - if (tparams.isEmpty) return (actuals1 corresponds formals1)(_ <:< _) - - if (targs.length == tparams.length) - isApplicableType(List(), tpe.instantiateTypeParams(tparams, targs)) - else if (targs.nonEmpty) - false - else { - val tvars = tparams map (TypeVar(_)) - (actuals1 corresponds formals1) { (actual, formal) => - val tp1 = actual.deconst.instantiateTypeParams(tparams, tvars) - val pt1 = actual.instantiateTypeParams(tparams, tvars) - tp1 <:< pt1 - } && - solve(tvars, tparams, List.fill(tparams.length)(COVARIANT), upper = false) + + def moreSpecific(method1: MethodSymbol, method2: MethodSymbol): Boolean = { + def points(m1: MethodSymbol, m2: MethodSymbol) = { + val p1 = if (signatureAsSpecific(m1, m2)) 1 else 0 + val p2 = if (scopeMoreSpecific(m1, m2)) 1 else 0 + p1 + p2 + } + points(method1, method2) > points(method2, method1) + } + + def combineInto ( + variadic: Boolean + )( + positional: Seq[Type], + named: Seq[(TermName, Type)] + )( + target: Seq[TermName], + defaults: Map[Int, Type] + ): Option[Seq[Type]] = { + + val offset = positional.length + val unfilled = target.zipWithIndex drop offset + val canAcceptAllNameVargs = named forall { case (argName, _) => + unfilled exists (_._1 == argName) + } + + val paramNamesUnique = { + named.length == named.map(_._1).distinct.length + } + + if (canAcceptAllNameVargs && paramNamesUnique) { + + val rest = unfilled map { case (paramName, paramIndex) => + val passedIn = named.collect { + case (argName, argType) if argName == paramName => argType + }.headOption + if (passedIn isDefined) passedIn + else defaults.get(paramIndex).map(_.asInstanceOf[Type]) + } + + val rest1 = { + if (variadic && !rest.isEmpty && !rest.last.isDefined) rest.init + else rest } + + + if (rest1 forall (_.isDefined)) { + val joined = positional ++ rest1.map(_.get) + val repeatedCollapsed = { + if (variadic) { + val (normal, repeated) = joined.splitAt(target.length - 1) + if (repeated.forall(_ =:= repeated.head)) Some(normal ++ repeated.headOption) + else None + } + else Some(joined) + } + if (repeatedCollapsed.exists(_.length == target.length)) + repeatedCollapsed + else if (variadic && repeatedCollapsed.exists(_.length == target.length - 1)) + repeatedCollapsed + else None + } else None + + } else None + } + + // Begin Reflection Helpers + + // Replaces a repeated parameter type at the end of the parameter list + // with a number of non-repeated parameter types in order to pad the + // list to be nargs in length + def extend(types: Seq[Type], nargs: Int): Seq[Type] = { + if (isVarArgTypes(types)) { + val repeatedType = types.last.normalize.typeArgs.head + types.init ++ Seq.fill(nargs - (types.length - 1))(repeatedType) + } else types + } + + // Replaces by-name parameters with their result type and + // TypeRefs with the thing they reference + def unwrap(paramType: Type): Type = paramType match { + case TypeRef(_, IntClass, _) => typeOf[Int] + case TypeRef(_, LongClass, _) => typeOf[Long] + case TypeRef(_, ShortClass, _) => typeOf[Short] + case TypeRef(_, ByteClass, _) => typeOf[Byte] + case TypeRef(_, CharClass, _) => typeOf[Char] + case TypeRef(_, FloatClass, _) => typeOf[Float] + case TypeRef(_, DoubleClass, _) => typeOf[Double] + case TypeRef(_, BooleanClass, _) => typeOf[Boolean] + case TypeRef(_, UnitClass, _) => typeOf[Unit] + case TypeRef(_, NullClass, _) => typeOf[Null] + case TypeRef(_, AnyClass, _) => typeOf[Any] + case TypeRef(_, NothingClass, _) => typeOf[Nothing] + case TypeRef(_, AnyRefClass, _) => typeOf[AnyRef] + case TypeRef(_, ByNameParamClass, List(resultType)) => unwrap(resultType) + case t: Type => t + } + + // Gives the names of the parameters to a method + def paramNames(signature: Type): Seq[TermName] = signature match { + case PolyType(_, resultType) => paramNames(resultType) + case MethodType(params, _) => params.map(_.name.asInstanceOf[TermName]) + case NullaryMethodType(_) => Seq.empty + } + + def valParams(signature: Type): Seq[TermSymbol] = signature match { + case PolyType(_, resultType) => valParams(resultType) + case MethodType(params, _) => params.map(_.asTermSymbol) + case NullaryMethodType(_) => Seq.empty + } + + // Returns a map from parameter index to default argument type + def defaultTypes(method: MethodSymbol): Map[Int, Type] = { + val typeSig = substituteTypeParams(method) + val owner = method.owner + valParams(typeSig).zipWithIndex.filter(_._1.hasFlag(Flag.DEFAULTPARAM)).map { case(_, index) => + val name = nme.defaultGetterName(method.name.decodedName, index + 1) + val default = owner.asType member name + index -> default.typeSignature.asInstanceOf[NullaryMethodType].resultType + }.toMap + } + + // True if any of method's parameters have default values. False otherwise. + def usesDefault(method: MethodSymbol): Boolean = valParams(method.typeSignature) drop(posVargTypes).length exists { param => + (param hasFlag Flag.DEFAULTPARAM) && nameVargTypes.forall { case (argName, _) => + param.name != argName + } + } + + // The number of type parameters that the method takes + def numTypeParams(x: MethodSymbol): Int = { + x.typeSignature.typeParams.length + } + + def substituteTypeParams(m: MethodSymbol): Type = { + (pre memberType m) match { + case m: MethodType => m + case n: NullaryMethodType => n + case PolyType(tparams, rest) => rest.substituteTypes(tparams, targs.toList) } - isApplicableType(List(), pre.memberType(alt)) } - def isAsGood(alt1: Symbol, alt2: Symbol): Boolean = { - alt1 == alt2 || - alt2 == NoSymbol || { - val (tparams, formals) = firstParams(pre memberType alt1) - isApplicable(alt2, tparams map (_.tpe), formals) + + // Begin Selection Helpers + + def select( + alternatives: Seq[MethodSymbol], + filters: Seq[Seq[MethodSymbol] => Seq[MethodSymbol]] + ): Seq[MethodSymbol] = + filters.foldLeft(alternatives)((a, f) => { + if (a.size > 1) f(a) else a + }) + + // Drop arguments that take the wrong number of type + // arguments. + val posTargLength: Seq[MethodSymbol] => Seq[MethodSymbol] = _.filter { alt => + numTypeParams(alt) == targs.length + } + + // Drop methods that are not applicable to the arguments + val applicable: Seq[MethodSymbol] => Seq[MethodSymbol] = _.filter { alt => + // Note: combine returns None if a is not applicable and + // None.exists(_ => true) == false + val paramTypes = + valParams(substituteTypeParams(alt)).map(p => unwrap(p.typeSignature)) + val variadic = isVarArgTypes(paramTypes) + val maybeArgTypes = + combineInto(variadic)(posVargTypes, nameVargTypes)(paramNames(alt.typeSignature), defaultTypes(alt)) + maybeArgTypes exists { argTypes => + if (isVarArgTypes(argTypes) && !isVarArgTypes(paramTypes)) false + else { + val a = argTypes + val p = extend(paramTypes, argTypes.length) + (a corresponds p)(_ <:< _) } + } } - assert(isOverloaded) - val applicables = alternatives filter (isApplicable(_, targs.toList, actuals)) - def winner(alts: List[Symbol]) = - ((NoSymbol: Symbol) /: alts)((best, alt) => if (isAsGood(alt, best)) alt else best) - val best = winner(applicables) - if (best == winner(applicables.reverse)) best else NoSymbol + + // Always prefer methods that don't need to use default + // arguments over those that do. + // e.g. when resolving foo(1), prefer def foo(x: Int) over + // def foo(x: Int, y: Int = 4) + val noDefaults: Seq[MethodSymbol] => Seq[MethodSymbol] = + _ filterNot usesDefault + + // Try to select the most specific method. If that's not possible, + // return all of the candidates (this will likely cause an error + // higher up in the call stack) + val mostSpecific: Seq[MethodSymbol] => Seq[MethodSymbol] = { alts => + val sorted = alts.sortWith(moreSpecific) + val mostSpecific = sorted.head + val agreeTest: MethodSymbol => Boolean = + moreSpecific(mostSpecific, _) + val disagreeTest: MethodSymbol => Boolean = + moreSpecific(_, mostSpecific) + if (!sorted.tail.forall(agreeTest)) { + mostSpecific +: sorted.tail.filterNot(agreeTest) + } else if (sorted.tail.exists(disagreeTest)) { + mostSpecific +: sorted.tail.filter(disagreeTest) + } else { + Seq(mostSpecific) + } + } + + def finalResult(t: Type): Type = t match { + case PolyType(_, rest) => finalResult(rest) + case MethodType(_, result) => finalResult(result) + case NullaryMethodType(result) => finalResult(result) + case t: Type => t + } + + // If a result type is given, drop alternatives that don't meet it + val resultType: Seq[MethodSymbol] => Seq[MethodSymbol] = + if (expected == NoType) identity + else _.filter { alt => + finalResult(substituteTypeParams(alt)) <:< expected + } + + def defaultFilteringOps = + Seq(posTargLength, resultType, applicable, noDefaults, mostSpecific) + + // Begin Method Proper + + + val alts = alternatives.map(_.asMethodSymbol) + + val selection = select(alts, defaultFilteringOps) + + val knownApplicable = applicable(selection) + + if (knownApplicable.size == 1) knownApplicable.head + else NoSymbol } } diff --git a/test/files/run/reflect-overload.scala b/test/files/run/reflect-overload.scala deleted file mode 100644 index 870a200813..0000000000 --- a/test/files/run/reflect-overload.scala +++ /dev/null @@ -1,19 +0,0 @@ -import scala.reflect.runtime.universe._ -import scala.reflect.runtime.{currentMirror => cm} - -object Test extends App { - - val s = "hello world" - val m = cm.reflect(s) - val sc = m.symbol - val st = sc.asType - val meth = (st member newTermName("indexOf")).asTermSymbol - val IntType = definitions.IntClass.asType - val indexOf = (meth resolveOverloaded(actuals = List(IntType))).asMethodSymbol - assert(m.reflectMethod(indexOf)('w') == 6) - assert((m.reflectMethod(indexOf)('w') match { case x: Int => x }) == 6) - - val meth2 = (st member newTermName("substring")).asTermSymbol - val substring = (meth2 resolveOverloaded(actuals = List(IntType, IntType))).asMethodSymbol - assert(m.reflectMethod(substring)(2, 6) == "llo ") -} diff --git a/test/files/run/reflect-resolveoverload-bynameparam.scala b/test/files/run/reflect-resolveoverload-bynameparam.scala new file mode 100644 index 0000000000..7fb8c82ab8 --- /dev/null +++ b/test/files/run/reflect-resolveoverload-bynameparam.scala @@ -0,0 +1,32 @@ + +class A +class B extends A + +class C { + def foo(x: => Int)(y: String) = x + def foo(x: String)(y: List[_]) = x + def foo(x: => A)(y: Array[_]) = 1 + def foo(x: A)(y: Seq[_]) = 2 + def foo(x: B)(y: Map[_, _]) = 4 +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + val c = new C + val im = cm.reflect(c) + val t = u.typeOf[C] member u.newTermName("foo") asTermSymbol + val f1 = t.resolveOverloaded(posVargs = List(u.typeOf[Int])) asMethodSymbol + val f2 = t.resolveOverloaded(posVargs = List(u.typeOf[String])) asMethodSymbol + val f3 = t.resolveOverloaded(posVargs = List(u.typeOf[A])) asMethodSymbol + val f4 = t.resolveOverloaded(posVargs = List(u.typeOf[B])) asMethodSymbol + val m1 = im.reflectMethod(f1) + val m2 = im.reflectMethod(f2) + val m3 = im.reflectMethod(f3) + val m4 = im.reflectMethod(f4) + assert(m1(() => 1, null) == c.foo(1)(null)) + assert(m2("a", null) == c.foo("a")(null)) + assert(m3(new A, null) == c.foo(new A)(null)) + assert(m4(new B, null) == c.foo(new B)(null)) +} + diff --git a/test/files/run/reflect-resolveoverload-expected.scala b/test/files/run/reflect-resolveoverload-expected.scala new file mode 100644 index 0000000000..1378090309 --- /dev/null +++ b/test/files/run/reflect-resolveoverload-expected.scala @@ -0,0 +1,43 @@ + +class A { + override def equals(x: Any) = { + x.isInstanceOf[A] && !x.isInstanceOf[B] + } +} +class B extends A { + override def equals(x: Any) = { + x.isInstanceOf[B] + } +} + +class C { + def a(x: String) = 1 + def a(x: Array[_]) = "a" + def b(x: String) = new A + def b(x: Array[_]) = new B + def c(x: String) = new B + def c(x: Array[_]) = "a" +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + val c = new C + val im = cm.reflect(c) + def invoke(s: String, expectedType: u.Type, expectedResult: Any) { + val ol = (u.typeOf[C] member u.newTermName(s)).asTermSymbol + val methodSym = ol.resolveOverloaded(posVargs = List(u.typeOf[Null]), expected = expectedType).asMethodSymbol + val sig = methodSym.typeSignature.asInstanceOf[u.MethodType] + val method = im.reflectMethod(methodSym) + assert(method(null) == expectedResult) + } + + invoke("a", u.typeOf[Int], c.a(null): Int) + invoke("a", u.typeOf[String], c.a(null): String) + invoke("b", u.typeOf[B], c.b(null): B) + invoke("c", u.typeOf[A], c.c(null): A) + invoke("c", u.typeOf[A], c.c(null): A) + invoke("c", u.typeOf[B], c.c(null): B) + invoke("c", u.typeOf[String], c.c(null): String) + +} diff --git a/test/files/run/reflect-resolveoverload-invalid.scala b/test/files/run/reflect-resolveoverload-invalid.scala new file mode 100644 index 0000000000..def28ccbb4 --- /dev/null +++ b/test/files/run/reflect-resolveoverload-invalid.scala @@ -0,0 +1,43 @@ + +class A +class B extends A + +class C { + def a(x: Int) = 1 + def a(x: String) = 2 + def b(x: B) = 3 + def c(x: A, y: B) = 4 + def c(x: B, y: A) = 5 + def d[T](x: Int) = 6 + def d(x: String) = 7 + def e(x: A) = 8 + def e(x: =>B) = 9 +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + + val x = new C + val t = u.typeOf[C] + + val a = t member u.newTermName("a") asTermSymbol + val b = t member u.newTermName("b") asTermSymbol + val c = t member u.newTermName("c") asTermSymbol + val d = t member u.newTermName("d") asTermSymbol + val e = t member u.newTermName("e") asTermSymbol + + val n1 = a.resolveOverloaded(posVargs = List(u.typeOf[Char])) + val n2 = b.resolveOverloaded(posVargs = List(u.typeOf[A])) + val n3 = c.resolveOverloaded(posVargs = List(u.typeOf[B], u.typeOf[B])) + val n4 = d.resolveOverloaded(targs = List(u.typeOf[Int])) + val n5 = d.resolveOverloaded() + val n6 = e.resolveOverloaded(posVargs = List(u.typeOf[B])) + + assert(n1 == u.NoSymbol) + assert(n2 == u.NoSymbol) + assert(n3 == u.NoSymbol) + assert(n4 == u.NoSymbol) + assert(n5 == u.NoSymbol) + assert(n6 == u.NoSymbol) +} diff --git a/test/files/run/reflect-resolveoverload-named.scala b/test/files/run/reflect-resolveoverload-named.scala new file mode 100644 index 0000000000..017ec85c0d --- /dev/null +++ b/test/files/run/reflect-resolveoverload-named.scala @@ -0,0 +1,26 @@ + +class A { + def foo(x: String, y: Int) = 1 + def foo(x: Int, y: String) = 2 +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + val a = new A + val im = cm.reflect(a) + val tpe = u.typeOf[A] + val overloaded = tpe member u.newTermName("foo") asTermSymbol + val ms1 = + overloaded resolveOverloaded(nameVargs = Seq((u.newTermName("x"), u.typeOf[String]), (u.newTermName("y"), u.typeOf[Int]))) + val ms2 = + overloaded resolveOverloaded(nameVargs = Seq((u.newTermName("y"), u.typeOf[Int]), (u.newTermName("x"), u.typeOf[String]))) + val ms3 = + overloaded resolveOverloaded(nameVargs = Seq((u.newTermName("x"), u.typeOf[Int]), (u.newTermName("y"), u.typeOf[String]))) + val ms4 = + overloaded resolveOverloaded(nameVargs = Seq((u.newTermName("y"), u.typeOf[String]), (u.newTermName("x"), u.typeOf[Int]))) + assert(im.reflectMethod(ms1 asMethodSymbol)("A", 1) == 1) + assert(im.reflectMethod(ms2 asMethodSymbol)("A", 1) == 1) + assert(im.reflectMethod(ms3 asMethodSymbol)(1, "A") == 2) + assert(im.reflectMethod(ms4 asMethodSymbol)(1, "A") == 2) +} diff --git a/test/files/run/reflect-resolveoverload-targs.scala b/test/files/run/reflect-resolveoverload-targs.scala new file mode 100644 index 0000000000..888b2f0c15 --- /dev/null +++ b/test/files/run/reflect-resolveoverload-targs.scala @@ -0,0 +1,29 @@ + +import reflect.runtime.{universe=>u} +import scala.reflect.runtime.{currentMirror => cm} + +class C { + def foo[T: u.TypeTag](x: String) = 1 + def foo[T: u.TypeTag, S: u.TypeTag](x: String) = 2 +} + +object Test extends App { + val c = new C + val im = cm.reflect(c) + val foo = u.typeOf[C] member u.newTermName("foo") asTermSymbol + val f1 = foo.resolveOverloaded( + targs = Seq(u.typeOf[Int]), + posVargs = Seq(u.typeOf[String]) + ) + + val f2 = foo.resolveOverloaded( + targs = Seq(u.typeOf[Int], + u.typeOf[Int]), posVargs = Seq(u.typeOf[String]) + ) + + val m1 = im.reflectMethod(f1 asMethodSymbol) + val m2 = im.reflectMethod(f2 asMethodSymbol) + + assert(m1("a", u.typeTag[Int]) == c.foo[Int]("a")) + assert(m2("a", u.typeTag[Int], u.typeTag[Int]) == c.foo[Int, Int]("a")) +} diff --git a/test/files/run/reflect-resolveoverload-tparm-substitute.scala b/test/files/run/reflect-resolveoverload-tparm-substitute.scala new file mode 100644 index 0000000000..22e7bcd40a --- /dev/null +++ b/test/files/run/reflect-resolveoverload-tparm-substitute.scala @@ -0,0 +1,77 @@ + +class A +class B extends A + +class C { + def foo[T](x: T) = x + def foo(x: Int) = "a" + def foo(x: A) = x +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + val c = new C + val im = cm.reflect(c) + val term = u.typeOf[C] member u.newTermName("foo") asTermSymbol + + val f1 = term.resolveOverloaded( + posVargs = List(u.typeOf[Int]), + expected = u.typeOf[String] + ) + + val f2 = term.resolveOverloaded( + targs = List(u.typeOf[String]), + posVargs = List(u.typeOf[String]), + expected = u.typeOf[String] + ) + + val f3 = term.resolveOverloaded( + posVargs = List(u.typeOf[A]), + expected = u.typeOf[A] + ) + + val f4 = term.resolveOverloaded( + targs = List(u.typeOf[A]), + posVargs = List(u.typeOf[A]), + expected = u.typeOf[A] + ) + + val f5 = term.resolveOverloaded( + targs = List(u.typeOf[B]), + posVargs = List(u.typeOf[B]), + expected = u.typeOf[B] + ) + + val f6 = term.resolveOverloaded( + targs = List(u.typeOf[B]), + posVargs = List(u.typeOf[B]), + expected = u.typeOf[A] + ) + + val f7 = term.resolveOverloaded( + targs = List(u.typeOf[A]), + posVargs = List(u.typeOf[B]), + expected = u.typeOf[A] + ) + + val m1 = im.reflectMethod(f1 asMethodSymbol) + val m2 = im.reflectMethod(f2 asMethodSymbol) + val m3 = im.reflectMethod(f3 asMethodSymbol) + val m4 = im.reflectMethod(f4 asMethodSymbol) + val m5 = im.reflectMethod(f5 asMethodSymbol) + val m6 = im.reflectMethod(f6 asMethodSymbol) + val m7 = im.reflectMethod(f7 asMethodSymbol) + + val a = new A + val b = new B + assert(m1(2) == (c.foo(2): String)) + assert(m2("xyz") == (c.foo[String]("xyz"): String)) + assert(m3(a) == (c.foo(a): A)) + assert(m4(a) == (c.foo[A](a): A)) + assert(m5(b) == (c.foo[B](b): B)) + assert(m6(b) == (c.foo[B](b): A)) + assert(m7(b) == (c.foo[A](b): A)) + + +} diff --git a/test/files/run/reflect-resolveoverload-variadic.scala b/test/files/run/reflect-resolveoverload-variadic.scala new file mode 100644 index 0000000000..8e2e15600f --- /dev/null +++ b/test/files/run/reflect-resolveoverload-variadic.scala @@ -0,0 +1,27 @@ + +class C { + def foo(x: Int*) = 1 + x.sum + def foo(x: String) = 2 +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + val c = new C + val im = cm.reflect(c) + val foo = u.typeOf[C] member u.newTermName("foo") asTermSymbol + val f0 = foo.resolveOverloaded() + val f1 = foo.resolveOverloaded(posVargs = Seq(u.typeOf[Int])) + val f2 = foo.resolveOverloaded(posVargs = Seq(u.typeOf[Int], u.typeOf[Int])) + val f3 = foo.resolveOverloaded(posVargs = Seq(u.typeOf[String])) + + val m0 = im.reflectMethod(f0 asMethodSymbol) + val m1 = im.reflectMethod(f1 asMethodSymbol) + val m2 = im.reflectMethod(f2 asMethodSymbol) + val m3 = im.reflectMethod(f3 asMethodSymbol) + + assert(m0(Seq()) == c.foo()) + assert(m1(Seq(1)) == c.foo(1)) + assert(m2(Seq(4, 9)) == c.foo(4, 9)) + assert(m3("abc") == c.foo("abc")) +} diff --git a/test/files/run/reflect-resolveoverload1.scala b/test/files/run/reflect-resolveoverload1.scala new file mode 100644 index 0000000000..a859a0ec4e --- /dev/null +++ b/test/files/run/reflect-resolveoverload1.scala @@ -0,0 +1,19 @@ +import scala.reflect.runtime.universe._ +import scala.reflect.runtime.{currentMirror => cm} + +object Test extends App { + + val s = "hello world" + val m = cm.reflect(s) + val sc = m.symbol + val st = sc.asType + val meth = (st member newTermName("indexOf")).asTermSymbol + val IntType = definitions.IntClass.asType + val indexOf = (meth resolveOverloaded(posVargs = List(IntType))).asMethodSymbol + assert(m.reflectMethod(indexOf)('w') == 6) + assert((m.reflectMethod(indexOf)('w') match { case x: Int => x }) == 6) + + val meth2 = (st member newTermName("substring")).asTermSymbol + val substring = (meth2 resolveOverloaded(posVargs = List(IntType, IntType))).asMethodSymbol + assert(m.reflectMethod(substring)(2, 6) == "llo ") +} diff --git a/test/files/run/reflect-resolveoverload2.scala b/test/files/run/reflect-resolveoverload2.scala new file mode 100644 index 0000000000..b5f719814b --- /dev/null +++ b/test/files/run/reflect-resolveoverload2.scala @@ -0,0 +1,40 @@ +class A +class B extends A + +class C { + def a(x: Int) = 1 + def a(x: String) = 2 + //def b(x: => Int)(s: String) = 1 + //def b(x: => String)(a: Array[_]) = 2 + def c(x: A) = 1 + def c(x: B) = 2 + //def d(x: => A)(s: String) = 1 + //def d(x: => B)(a: Array[_]) = 2 + def e(x: A) = 1 + def e(x: B = new B) = 2 +} + +object Test extends App { + val cm = reflect.runtime.currentMirror + val u = cm.universe + val c = new C + val im = cm.reflect(c) + def invoke(s: String, arg: Any, argType: u.Type): Int = { + val ol = u.typeOf[C] member u.newTermName(s) asTermSymbol + val methodSym = ol.resolveOverloaded(posVargs = List(argType)) asMethodSymbol + val sig = methodSym.typeSignature.asInstanceOf[u.MethodType] + val method = im.reflectMethod(methodSym) + if (sig.resultType.kind == "MethodType") method(arg, null).asInstanceOf[Int] + else method(arg).asInstanceOf[Int] + } + assert(c.a(1) == invoke("a", 1, u.typeOf[Int])) + assert(c.a("a") == invoke("a", "a", u.typeOf[String])) + //assert(c.b(1)(null) == invoke("b", 1, u.typeOf[Int])) + //assert(c.b("a")(null) == invoke("b", "a", u.typeOf[String])) + assert(c.c(new A) == invoke("c", new A, u.typeOf[A])) + assert(c.c(new B) == invoke("c", new B, u.typeOf[B])) + //assert(c.d(new A)(null) == invoke("d", new A, u.typeOf[A])) + //assert(c.d(new B)(null) == invoke("d", new B, u.typeOf[B])) + assert(c.e(new A) == invoke("e", new A, u.typeOf[A])) + assert(c.e(new B) == invoke("e", new B, u.typeOf[B])) +} -- cgit v1.2.3 From c282c7c56cbb734cca859dc5a825e70aee976a1a Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 9 Jul 2012 20:05:13 +0200 Subject: Removing the actor migration undeterministic test. --- test/files/jvm/actmig-loop-react.scala | 188 --------------------------------- 1 file changed, 188 deletions(-) delete mode 100644 test/files/jvm/actmig-loop-react.scala (limited to 'test') diff --git a/test/files/jvm/actmig-loop-react.scala b/test/files/jvm/actmig-loop-react.scala deleted file mode 100644 index d714b26594..0000000000 --- a/test/files/jvm/actmig-loop-react.scala +++ /dev/null @@ -1,188 +0,0 @@ -import scala.actors.MigrationSystem._ -import scala.actors.Actor._ -import scala.actors.{ Actor, StashingActor, ActorRef, Props, MigrationSystem, PoisonPill } -import java.util.concurrent.{ TimeUnit, CountDownLatch } -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.util.duration._ -import scala.concurrent.{ Promise, Await } - - -object Test { - val finishedLWCR, finishedTNR, finishedEH = Promise[Boolean] - val finishedLWCR1, finishedTNR1, finishedEH1 = Promise[Boolean] - - def testLoopWithConditionReact() = { - // Snippet showing composition of receives - // Loop with Condition Snippet - before - val myActor = actor { - var c = true - loopWhile(c) { - react { - case x: Int => - // do task - println("do task") - if (x == 42) { - c = false - finishedLWCR1.success(true) - } - } - } - } - - myActor.start() - myActor ! 1 - myActor ! 42 - - Await.ready(finishedLWCR1.future, 5 seconds) - - // Loop with Condition Snippet - migrated - val myAkkaActor = MigrationSystem.actorOf(Props(() => new StashingActor { - - def receive = { - case x: Int => - // do task - println("do task") - if (x == 42) { - finishedLWCR.success(true) - context.stop(self) - } - } - }, "default-stashing-dispatcher")) - myAkkaActor ! 1 - myAkkaActor ! 42 - } - - def testNestedReact() = { - // Snippet showing composition of receives - // Loop with Condition Snippet - before - val myActor = actor { - var c = true - loopWhile(c) { - react { - case x: Int => - // do task - println("do task " + x) - if (x == 42) { - c = false - finishedTNR1.success(true) - } else - react { - case y: String => - println("do string " + y) - } - println("after react") - } - } - } - myActor.start() - - myActor ! 1 - myActor ! "I am a String" - myActor ! 42 - - Await.ready(finishedTNR1.future, 5 seconds) - - // Loop with Condition Snippet - migrated - val myAkkaActor = MigrationSystem.actorOf(Props(() => new StashingActor { - - def receive = { - case x: Int => - // do task - println("do task " + x) - if (x == 42) { - finishedTNR.success(true) - context.stop(self) - } else - context.become(({ - case y: String => - println("do string " + y) - }: Receive).andThen(x => { - unstashAll() - context.unbecome() - }).orElse { case x => stash() }) - } - }, "default-stashing-dispatcher")) - - myAkkaActor ! 1 - myAkkaActor ! "I am a String" - myAkkaActor ! 42 - - } - - def exceptionHandling() = { - // Stashing actor with act and exception handler - val myActor = MigrationSystem.actorOf(Props(() => new StashingActor { - - def receive = { case _ => println("Dummy method.") } - override def act() = { - loop { - react { - case "fail" => - throw new Exception("failed") - case "work" => - println("working") - case "die" => - finishedEH1.success(true) - exit() - } - } - } - - override def exceptionHandler = { - case x: Exception => println("scala got exception") - } - - }, "default-stashing-dispatcher")) - - myActor ! "work" - myActor ! "fail" - myActor ! "die" - - Await.ready(finishedEH1.future, 5 seconds) - // Stashing actor in Akka style - val myAkkaActor = MigrationSystem.actorOf(Props(() => new StashingActor { - def receive = PFCatch({ - case "fail" => - throw new Exception("failed") - case "work" => - println("working") - case "die" => - finishedEH.success(true) - context.stop(self) - }, { case x: Exception => println("akka got exception") }) - }, "default-stashing-dispatcher")) - - myAkkaActor ! "work" - myAkkaActor ! "fail" - myAkkaActor ! "die" - } - - def main(args: Array[String]) = { - testLoopWithConditionReact() - Await.ready(finishedLWCR.future, 5 seconds) - exceptionHandling() - Await.ready(finishedEH.future, 5 seconds) - testNestedReact() - Await.ready(finishedTNR.future, 5 seconds) - } - -} - -// As per Jim Mcbeath's blog (http://jim-mcbeath.blogspot.com/2008/07/actor-exceptions.html) -class PFCatch(f: PartialFunction[Any, Unit], handler: PartialFunction[Exception, Unit]) - extends PartialFunction[Any, Unit] { - - def apply(x: Any) = { - try { - f(x) - } catch { - case e: Exception if handler.isDefinedAt(e) => handler(e) - } - } - - def isDefinedAt(x: Any) = f.isDefinedAt(x) -} - -object PFCatch { - def apply(f: PartialFunction[Any, Unit], handler: PartialFunction[Exception, Unit]) = new PFCatch(f, handler) -} -- cgit v1.2.3 From 4496c5daa9c573df6d4e2c85a34c37eb9933d1bd Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Mon, 9 Jul 2012 12:59:29 -0400 Subject: Collection of updates to SIP-14 (scala.concurrent) Developed by Viktor Klang and Havoc Pennington - add Promise.isCompleted - add Future.successful and Future.failed - add ExecutionContextExecutor and ExecutionContextExecutorService for Java interop - remove defaultExecutionContext as default parameter value from promise and future - add ExecutionContext.Implicits.global which must be explicitly imported, rather than the previous always-available value for the implicit EC - remove currentExecutionContext, since it could create bugs by being out of sync with the implicit ExecutionContext - remove Future task batching (_taskStack) and Future.releaseStack This optimization should instead be implemented either in a specific thread pool or in a specific ExecutionContext. Some pools or ExecutionContexts may not want or need it. In this patch, the defaultExecutionContext does not keep the batching optimization. Whether it should have it should perhaps be determined through benchmarking. - move internalBlockingCall to BlockContext and remove currentExecutionContext In this patch, BlockContext must be implemented by Thread.currentThread, so the thread pool is the only place you can add custom hooks to be run when blocking. We implement BlockContext for the default ForkJoinWorkerThread in terms of ForkJoinPool.ManagedBlocker. - add public BlockContext.current and BlockContext.withBlockContext These allow an ExecutionContext or other code to override the BlockContext for the current thread. With this API, the BlockContext is customizable without creating a new pool of threads. BlockContext.current is needed to obtain the previous BlockContext before you push, so you can "chain up" to it if desired. BlockContext.withBlockContext is used to override the context for a given piece of code. - move isFutureThrowable into impl.Future - add implicitNotFound to ExecutionContext - remove default global EC from future {} and promise {} - add ExecutionContext.global for explicit use of the global default EC, replaces defaultExecutionContext - add a timeout to scala-concurrent-tck tests that block on SyncVar (so tests time out rather than hang) - insert blocking{} calls into concurrent tck to fix deadlocking - add NonFatal.apply and tests for NonFatal - add OnCompleteRunnable marker trait This would allow an ExecutionContext to distinguish a Runnable originating from Future.onComplete (all callbacks on Future end up going through onComplete). - rename ListenerRunnable to CallbackRunnable and use for KeptPromise too Just adds some clarity and consistency. --- .../scala/collection/parallel/TaskSupport.scala | 2 +- src/library/scala/concurrent/BlockContext.scala | 81 +++++++++++ .../scala/concurrent/ConcurrentPackageObject.scala | 34 +---- src/library/scala/concurrent/DelayedLazyVal.scala | 5 +- .../scala/concurrent/ExecutionContext.scala | 74 ++++++---- src/library/scala/concurrent/Future.scala | 29 +++- src/library/scala/concurrent/Promise.scala | 9 ++ .../concurrent/impl/ExecutionContextImpl.scala | 61 +++----- src/library/scala/concurrent/impl/Future.scala | 102 ++----------- src/library/scala/concurrent/impl/Promise.scala | 34 +++-- src/library/scala/util/control/NonFatal.scala | 23 +-- test/files/jvm/future-spec/FutureTests.scala | 124 ++++++++-------- test/files/jvm/future-spec/PromiseTests.scala | 15 +- test/files/jvm/non-fatal-tests.scala | 47 ++++++ test/files/jvm/scala-concurrent-tck.scala | 159 +++++++++++++++------ 15 files changed, 478 insertions(+), 321 deletions(-) create mode 100644 src/library/scala/concurrent/BlockContext.scala create mode 100644 test/files/jvm/non-fatal-tests.scala (limited to 'test') diff --git a/src/library/scala/collection/parallel/TaskSupport.scala b/src/library/scala/collection/parallel/TaskSupport.scala index 2eaa861429..3d27f619bb 100644 --- a/src/library/scala/collection/parallel/TaskSupport.scala +++ b/src/library/scala/collection/parallel/TaskSupport.scala @@ -48,7 +48,7 @@ extends TaskSupport with AdaptiveWorkStealingThreadPoolTasks * By default, parallel collections are parametrized with this task support object, so parallel collections * share the same execution context backend as the rest of the `scala.concurrent` package. */ -class ExecutionContextTaskSupport(val environment: ExecutionContext = scala.concurrent.defaultExecutionContext) +class ExecutionContextTaskSupport(val environment: ExecutionContext = scala.concurrent.ExecutionContext.global) extends TaskSupport with ExecutionContextTasks diff --git a/src/library/scala/concurrent/BlockContext.scala b/src/library/scala/concurrent/BlockContext.scala new file mode 100644 index 0000000000..a5b878c546 --- /dev/null +++ b/src/library/scala/concurrent/BlockContext.scala @@ -0,0 +1,81 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.concurrent + +import java.lang.Thread +import scala.concurrent.util.Duration + +/** + * A context to be notified by `scala.concurrent.blocking()` when + * a thread is about to block. In effect this trait provides + * the implementation for `scala.concurrent.blocking()`. `scala.concurrent.blocking()` + * locates an instance of `BlockContext` by first looking for one + * provided through `BlockContext.withBlockContext()` and failing that, + * checking whether `Thread.currentThread` is an instance of `BlockContext`. + * So a thread pool can have its `java.lang.Thread` instances implement + * `BlockContext`. There's a default `BlockContext` used if the thread + * doesn't implement `BlockContext`. + * + * Typically, you'll want to chain to the previous `BlockContext`, + * like this: + * {{{ + * val oldContext = BlockContext.current + * val myContext = new BlockContext { + * override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { + * // you'd have code here doing whatever you need to do + * // when the thread is about to block. + * // Then you'd chain to the previous context: + * oldContext.internalBlockingCall(awaitable, atMost) + * } + * } + * BlockContext.withBlockContext(myContext) { + * // then this block runs with myContext as the handler + * // for scala.concurrent.blocking + * } + * }}} + */ +trait BlockContext { + + /** Used internally by the framework; blocks execution for at most + * `atMost` time while waiting for an `awaitable` object to become ready. + * + * Clients should use `scala.concurrent.blocking` instead; this is + * the implementation of `scala.concurrent.blocking`, generally + * provided by a `scala.concurrent.ExecutionContext` or `java.util.concurrent.Executor`. + */ + def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T +} + +object BlockContext { + private object DefaultBlockContext extends BlockContext { + override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = + awaitable.result(atMost)(Await.canAwaitEvidence) + } + + private val contextLocal = new ThreadLocal[BlockContext]() { + override def initialValue = Thread.currentThread match { + case ctx: BlockContext => ctx + case _ => DefaultBlockContext + } + } + + /** Obtain the current thread's current `BlockContext`. */ + def current: BlockContext = contextLocal.get + + /** Pushes a current `BlockContext` while executing `body`. */ + def withBlockContext[T](blockContext: BlockContext)(body: => T): T = { + val old = contextLocal.get + try { + contextLocal.set(blockContext) + body + } finally { + contextLocal.set(old) + } + } +} diff --git a/src/library/scala/concurrent/ConcurrentPackageObject.scala b/src/library/scala/concurrent/ConcurrentPackageObject.scala index 330a2f0e25..86a86966ef 100644 --- a/src/library/scala/concurrent/ConcurrentPackageObject.scala +++ b/src/library/scala/concurrent/ConcurrentPackageObject.scala @@ -17,23 +17,6 @@ import language.implicitConversions /** This package object contains primitives for concurrent and parallel programming. */ abstract class ConcurrentPackageObject { - /** A global execution environment for executing lightweight tasks. - */ - lazy val defaultExecutionContext: ExecutionContext with Executor = impl.ExecutionContextImpl.fromExecutor(null: Executor) - - val currentExecutionContext = new ThreadLocal[ExecutionContext] - - val handledFutureException: PartialFunction[Throwable, Throwable] = { - case t: Throwable if isFutureThrowable(t) => t - } - - // TODO rename appropriately and make public - private[concurrent] def isFutureThrowable(t: Throwable) = t match { - case e: Error => false - case t: scala.util.control.ControlThrowable => false - case i: InterruptedException => false - case _ => true - } /* concurrency constructs */ @@ -46,8 +29,7 @@ abstract class ConcurrentPackageObject { * @param execctx the execution context on which the future is run * @return the `Future` holding the result of the computation */ - def future[T](body: =>T)(implicit execctx: ExecutionContext = defaultExecutionContext): Future[T] = - Future[T](body) + def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body) /** Creates a promise object which can be completed with a value. * @@ -55,8 +37,7 @@ abstract class ConcurrentPackageObject { * @param execctx the execution context on which the promise is created on * @return the newly created `Promise` object */ - def promise[T]()(implicit execctx: ExecutionContext = defaultExecutionContext): Promise[T] = - Promise[T]() + def promise[T]()(implicit execctx: ExecutionContext): Promise[T] = Promise[T]() /** Used to block on a piece of code which potentially blocks. * @@ -67,8 +48,7 @@ abstract class ConcurrentPackageObject { * - InterruptedException - in the case that a wait within the blockable object was interrupted * - TimeoutException - in the case that the blockable object timed out */ - def blocking[T](body: =>T): T = - blocking(impl.Future.body2awaitable(body), Duration.Inf) + def blocking[T](body: =>T): T = blocking(impl.Future.body2awaitable(body), Duration.Inf) /** Blocks on an awaitable object. * @@ -79,12 +59,8 @@ abstract class ConcurrentPackageObject { * - InterruptedException - in the case that a wait within the blockable object was interrupted * - TimeoutException - in the case that the blockable object timed out */ - def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = { - currentExecutionContext.get match { - case null => awaitable.result(atMost)(Await.canAwaitEvidence) - case ec => ec.internalBlockingCall(awaitable, atMost) - } - } + def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = + BlockContext.current.internalBlockingCall(awaitable, atMost) @inline implicit final def int2durationops(x: Int): DurationOps = new DurationOps(x) } diff --git a/src/library/scala/concurrent/DelayedLazyVal.scala b/src/library/scala/concurrent/DelayedLazyVal.scala index 96a66d83b6..91e41748f5 100644 --- a/src/library/scala/concurrent/DelayedLazyVal.scala +++ b/src/library/scala/concurrent/DelayedLazyVal.scala @@ -23,7 +23,7 @@ package scala.concurrent * @author Paul Phillips * @version 2.8 */ -class DelayedLazyVal[T](f: () => T, body: => Unit) { +class DelayedLazyVal[T](f: () => T, body: => Unit){ @volatile private[this] var _isDone = false private[this] lazy val complete = f() @@ -39,7 +39,8 @@ class DelayedLazyVal[T](f: () => T, body: => Unit) { */ def apply(): T = if (isDone) complete else f() - // TODO replace with scala.concurrent.future { ... } + // FIXME need to take ExecutionContext in constructor + import ExecutionContext.Implicits.global future { body _isDone = true diff --git a/src/library/scala/concurrent/ExecutionContext.scala b/src/library/scala/concurrent/ExecutionContext.scala index 436a17a33b..b486e5269e 100644 --- a/src/library/scala/concurrent/ExecutionContext.scala +++ b/src/library/scala/concurrent/ExecutionContext.scala @@ -9,58 +9,80 @@ package scala.concurrent - -import java.util.concurrent.atomic.{ AtomicInteger } -import java.util.concurrent.{ Executors, Future => JFuture, Callable, ExecutorService, Executor } +import java.util.concurrent.{ ExecutorService, Executor } import scala.concurrent.util.Duration -import scala.concurrent.forkjoin.{ ForkJoinPool, RecursiveTask => FJTask, RecursiveAction, ForkJoinWorkerThread } -import scala.collection.generic.CanBuildFrom -import collection._ - - +import scala.annotation.implicitNotFound +/** + * An `ExecutionContext` is an abstraction over an entity that can execute program logic. + */ +@implicitNotFound("Cannot find an implicit ExecutionContext, either require one yourself or import ExecutionContext.Implicits.global") trait ExecutionContext { /** Runs a block of code on this execution context. */ def execute(runnable: Runnable): Unit - /** Used internally by the framework - blocks execution for at most `atMost` time while waiting - * for an `awaitable` object to become ready. - * - * Clients should use `scala.concurrent.blocking` instead. - */ - def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T - /** Reports that an asynchronous computation failed. */ def reportFailure(t: Throwable): Unit } +/** + * Union interface since Java does not support union types + */ +trait ExecutionContextExecutor extends ExecutionContext with Executor + +/** + * Union interface since Java does not support union types + */ +trait ExecutionContextExecutorService extends ExecutionContextExecutor with ExecutorService + /** Contains factory methods for creating execution contexts. */ object ExecutionContext { - - implicit def defaultExecutionContext: ExecutionContext = scala.concurrent.defaultExecutionContext - + /** + * The `ExecutionContext` associated with the current `Thread` + */ + val currentExecutionContext: ThreadLocal[ExecutionContext] = new ThreadLocal //FIXME might want to set the initial value to an executionContext that throws an exception on execute and warns that it's not set + + /** + * This is the explicit global ExecutionContext, + * call this when you want to provide the global ExecutionContext explicitly + */ + def global: ExecutionContextExecutor = Implicits.global + + object Implicits { + /** + * This is the implicit global ExecutionContext, + * import this when you want to provide the global ExecutionContext implicitly + */ + implicit lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor) + } + /** Creates an `ExecutionContext` from the given `ExecutorService`. */ - def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit = defaultReporter): ExecutionContext with ExecutorService = + def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService = impl.ExecutionContextImpl.fromExecutorService(e, reporter) + + /** Creates an `ExecutionContext` from the given `ExecutorService` with the default Reporter. + */ + def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService = fromExecutorService(e, defaultReporter) /** Creates an `ExecutionContext` from the given `Executor`. */ - def fromExecutor(e: Executor, reporter: Throwable => Unit = defaultReporter): ExecutionContext with Executor = + def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(e, reporter) + + /** Creates an `ExecutionContext` from the given `Executor` with the default Reporter. + */ + def fromExecutor(e: Executor): ExecutionContextExecutor = fromExecutor(e, defaultReporter) - def defaultReporter: Throwable => Unit = { - // re-throwing `Error`s here causes an exception handling test to fail. - //case e: Error => throw e - case t => t.printStackTrace() - } - + /** The default reporter simply prints the stack trace of the `Throwable` to System.err. + */ + def defaultReporter: Throwable => Unit = { case t => t.printStackTrace() } } diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 2de0c57253..75a83d6ef8 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -8,7 +8,7 @@ package scala.concurrent - +import language.higherKinds import java.util.concurrent.{ ConcurrentLinkedQueue, TimeUnit, Callable } import java.util.concurrent.TimeUnit.{ NANOSECONDS => NANOS, MILLISECONDS ⇒ MILLIS } @@ -23,11 +23,9 @@ import scala.Option import scala.util.{Try, Success, Failure} import scala.annotation.tailrec -import scala.collection.mutable.Stack import scala.collection.mutable.Builder import scala.collection.generic.CanBuildFrom import scala.reflect.ClassTag -import language.higherKinds @@ -138,7 +136,7 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Left(t) if (isFutureThrowable(t) && callback.isDefinedAt(t)) => callback(t) + case Left(t) if (impl.Future.isFutureThrowable(t) && callback.isDefinedAt(t)) => callback(t) case _ => }(executor) @@ -580,6 +578,20 @@ object Future { classOf[Double] -> classOf[jl.Double], classOf[Unit] -> classOf[scala.runtime.BoxedUnit] ) + + /** Creates an already completed Future with the specified exception. + * + * @tparam T the type of the value in the future + * @return the newly created `Future` object + */ + def failed[T](exception: Throwable): Future[T] = Promise.failed(exception).future + + /** Creates an already completed Future with the specified result. + * + * @tparam T the type of the value in the future + * @return the newly created `Future` object + */ + def successful[T](result: T): Future[T] = Promise.successful(result).future /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. * @@ -710,5 +722,12 @@ object Future { } } - +/** A marker indicating that a `java.lang.Runnable` provided to `scala.concurrent.ExecutionContext` + * wraps a callback provided to `Future.onComplete`. + * All callbacks provided to a `Future` end up going through `onComplete`, so this allows an + * `ExecutionContext` to special-case callbacks that were executed by `Future` if desired. + */ +trait OnCompleteRunnable { + self: Runnable => +} diff --git a/src/library/scala/concurrent/Promise.scala b/src/library/scala/concurrent/Promise.scala index 578642966f..5d1b2c00b6 100644 --- a/src/library/scala/concurrent/Promise.scala +++ b/src/library/scala/concurrent/Promise.scala @@ -34,6 +34,15 @@ trait Promise[T] { */ def future: Future[T] + /** Returns whether the promise has already been completed with + * a value or an exception. + * + * $nonDeterministic + * + * @return `true` if the promise is already completed, `false` otherwise + */ + def isCompleted: Boolean + /** Completes the promise with either an exception or a value. * * @param result Either the value or the exception to complete the promise with. diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 4c6347dce0..551a444425 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -13,34 +13,34 @@ package scala.concurrent.impl import java.util.concurrent.{ Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit } import java.util.Collection import scala.concurrent.forkjoin._ -import scala.concurrent.{ ExecutionContext, Awaitable } +import scala.concurrent.{ BlockContext, ExecutionContext, Awaitable, ExecutionContextExecutor, ExecutionContextExecutorService } import scala.concurrent.util.Duration import scala.util.control.NonFatal -private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: Throwable => Unit) extends ExecutionContext with Executor { +private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: Throwable => Unit) extends ExecutionContextExecutor { val executor: Executor = es match { case null => createExecutorService case some => some } - - // to ensure that the current execution context thread local is properly set - def executorsThreadFactory = new ThreadFactory { - def newThread(r: Runnable) = new Thread(new Runnable { - override def run() { - scala.concurrent.currentExecutionContext.set(ExecutionContextImpl.this) - r.run() - } - }) - } - - // to ensure that the current execution context thread local is properly set + + // Implement BlockContext on FJP threads def forkJoinPoolThreadFactory = new ForkJoinPool.ForkJoinWorkerThreadFactory { - def newThread(fjp: ForkJoinPool) = new ForkJoinWorkerThread(fjp) { - override def onStart() { - scala.concurrent.currentExecutionContext.set(ExecutionContextImpl.this) + def newThread(fjp: ForkJoinPool) = new ForkJoinWorkerThread(fjp) with BlockContext { + override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { + var result: T = null.asInstanceOf[T] + ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker { + @volatile var isdone = false + def block(): Boolean = { + result = awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) // FIXME what happens if there's an exception thrown here? + isdone = true + true + } + def isReleasable = isdone + }) + result } } } @@ -68,7 +68,7 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: case NonFatal(t) => System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to Executors.newCachedThreadPool") t.printStackTrace(System.err) - Executors.newCachedThreadPool(executorsThreadFactory) //FIXME use the same desired parallelism here too? + Executors.newCachedThreadPool() //FIXME use the same desired parallelism here too? } def execute(runnable: Runnable): Unit = executor match { @@ -84,27 +84,6 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: case generic => generic execute runnable } - def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { - Future.releaseStack(this) - - executor match { - case fj: ForkJoinPool => - var result: T = null.asInstanceOf[T] - ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker { - @volatile var isdone = false - def block(): Boolean = { - result = awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) // FIXME what happens if there's an exception thrown here? - isdone = true - true - } - def isReleasable = isdone - }) - result - case _ => - awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) - } - } - def reportFailure(t: Throwable) = reporter(t) } @@ -112,8 +91,8 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: private[concurrent] object ExecutionContextImpl { def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextImpl = new ExecutionContextImpl(e, reporter) - def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextImpl with ExecutorService = - new ExecutionContextImpl(es, reporter) with ExecutorService { + def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextImpl with ExecutionContextExecutorService = + new ExecutionContextImpl(es, reporter) with ExecutionContextExecutorService { final def asExecutorService: ExecutorService = executor.asInstanceOf[ExecutorService] override def execute(command: Runnable) = executor.execute(command) override def shutdown() { asExecutorService.shutdown() } diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 8012ea6a93..073e6c4c9f 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -46,11 +46,19 @@ private[concurrent] object Future { def boxedType(c: Class[_]): Class[_] = if (c.isPrimitive) toBoxed(c) else c - private[impl] class PromiseCompletingTask[T](override val executor: ExecutionContext, body: => T) - extends Task { + // TODO rename appropriately and make public + private[concurrent] def isFutureThrowable(t: Throwable) = t match { + case e: Error => false + case t: scala.util.control.ControlThrowable => false + case i: InterruptedException => false + case _ => true + } + + private[impl] class PromiseCompletingRunnable[T](body: => T) + extends Runnable { val promise = new Promise.DefaultPromise[T]() - protected override def task() = { + override def run() = { promise complete { try Right(body) catch { case NonFatal(e) => @@ -63,90 +71,8 @@ private[concurrent] object Future { } def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = { - val task = new PromiseCompletingTask(executor, body) - task.dispatch() - - task.promise.future - } - - private[impl] val throwableId: Throwable => Throwable = identity _ - - // an optimization for batching futures - // TODO we should replace this with a public queue, - // so that it can be stolen from - // OR: a push to the local task queue should be so cheap that this is - // not even needed, but stealing is still possible - - private[impl] case class TaskStack(stack: Stack[Task], executor: ExecutionContext) - - private val _taskStack = new ThreadLocal[TaskStack]() - - private[impl] trait Task extends Runnable { - def executor: ExecutionContext - - // run the original callback (no dispatch) - protected def task(): Unit - - // we implement Runnable to avoid creating - // an extra object. run() runs ourselves with - // a TaskStack pushed, and then runs any - // other tasks that show up in the stack. - final override def run() = { - try { - val taskStack = TaskStack(Stack[Task](this), executor) - _taskStack set taskStack - while (taskStack.stack.nonEmpty) { - val next = taskStack.stack.pop() - require(next.executor eq executor) - try next.task() catch { case NonFatal(e) => executor reportFailure e } - } - } finally { - _taskStack.remove() - } - } - - // send the task to the running executor.execute() via - // _taskStack, or start a new executor.execute() - def dispatch(force: Boolean = false): Unit = - _taskStack.get match { - case stack if (stack ne null) && (executor eq stack.executor) && !force => stack.stack push this - case _ => executor.execute(this) - } - } - - private[impl] class ReleaseTask(override val executor: ExecutionContext, val elems: List[Task]) - extends Task { - protected override def task() = { - _taskStack.get.stack.elems = elems - } - } - - private[impl] def releaseStack(executor: ExecutionContext): Unit = - _taskStack.get match { - case stack if (stack ne null) && stack.stack.nonEmpty => - val tasks = stack.stack.elems - stack.stack.clear() - _taskStack.remove() - val release = new ReleaseTask(executor, tasks) - release.dispatch(force=true) - case null => - // do nothing - there is no local batching stack anymore - case _ => - _taskStack.remove() - } - - private[impl] class OnCompleteTask[T](override val executor: ExecutionContext, val onComplete: (Either[Throwable, T]) => Any) - extends Task { - private var value: Either[Throwable, T] = null - - protected override def task() = { - require(value ne null) // dispatch(value) must be called before dispatch() - onComplete(value) - } - - def dispatch(value: Either[Throwable, T]): Unit = { - this.value = value - dispatch() - } + val runnable = new PromiseCompletingRunnable(body) + executor.execute(runnable) + runnable.promise.future } } diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index c5060a2368..3ac34bef8a 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -11,11 +11,12 @@ package scala.concurrent.impl import java.util.concurrent.TimeUnit.{ NANOSECONDS, MILLISECONDS } -import scala.concurrent.{ Awaitable, ExecutionContext, blocking, CanAwait, TimeoutException, ExecutionException } +import scala.concurrent.{ Awaitable, ExecutionContext, blocking, CanAwait, OnCompleteRunnable, TimeoutException, ExecutionException } //import scala.util.continuations._ import scala.concurrent.util.Duration import scala.util import scala.annotation.tailrec +import scala.util.control.NonFatal //import scala.concurrent.NonDeterministic @@ -24,6 +25,21 @@ private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with Fu def future: this.type = this } +private class CallbackRunnable[T](val executor: ExecutionContext, val onComplete: (Either[Throwable, T]) => Any) extends Runnable with OnCompleteRunnable { + // must be filled in before running it + var value: Either[Throwable, T] = null + + override def run() = { + require(value ne null) // must set value to non-null before running! + try onComplete(value) catch { case NonFatal(e) => executor reportFailure e } + } + + def executeWithValue(v: Either[Throwable, T]): Unit = { + require(value eq null) // can't complete it twice + value = v + executor.execute(this) + } +} object Promise { @@ -94,10 +110,10 @@ object Promise { val resolved = resolveEither(value) (try { @tailrec - def tryComplete(v: Either[Throwable, T]): List[Future.OnCompleteTask[T]] = { + def tryComplete(v: Either[Throwable, T]): List[CallbackRunnable[T]] = { getState match { case raw: List[_] => - val cur = raw.asInstanceOf[List[Future.OnCompleteTask[T]]] + val cur = raw.asInstanceOf[List[CallbackRunnable[T]]] if (updateState(cur, v)) cur else tryComplete(v) case _ => null } @@ -107,19 +123,19 @@ object Promise { synchronized { notifyAll() } //Notify any evil blockers }) match { case null => false - case cs if cs.isEmpty => true - case cs => cs.foreach(c => c.dispatch(resolved)); true + case rs if rs.isEmpty => true + case rs => rs.foreach(r => r.executeWithValue(resolved)); true } } def onComplete[U](func: Either[Throwable, T] => U)(implicit executor: ExecutionContext): Unit = { - val bound = new Future.OnCompleteTask[T](executor, func) + val runnable = new CallbackRunnable[T](executor, func) @tailrec //Tries to add the callback, if already completed, it dispatches the callback to be executed def dispatchOrAddCallback(): Unit = getState match { - case r: Either[_, _] => bound.dispatch(r.asInstanceOf[Either[Throwable, T]]) - case listeners: List[_] => if (updateState(listeners, bound :: listeners)) () else dispatchOrAddCallback() + case r: Either[_, _] => runnable.executeWithValue(r.asInstanceOf[Either[Throwable, T]]) + case listeners: List[_] => if (updateState(listeners, runnable :: listeners)) () else dispatchOrAddCallback() } dispatchOrAddCallback() } @@ -139,7 +155,7 @@ object Promise { def onComplete[U](func: Either[Throwable, T] => U)(implicit executor: ExecutionContext): Unit = { val completedAs = value.get - (new Future.OnCompleteTask(executor, func)).dispatch(completedAs) + (new CallbackRunnable(executor, func)).executeWithValue(completedAs) } def ready(atMost: Duration)(implicit permit: CanAwait): this.type = this diff --git a/src/library/scala/util/control/NonFatal.scala b/src/library/scala/util/control/NonFatal.scala index 9da2f63307..5137f0f2f5 100644 --- a/src/library/scala/util/control/NonFatal.scala +++ b/src/library/scala/util/control/NonFatal.scala @@ -23,16 +23,23 @@ package scala.util.control * // dangerous stuff * } catch { * case NonFatal(e) => log.error(e, "Something not that bad.") + * // or + * case e if NonFatal(e) => log.error(e, "Something not that bad.") * } * }}} */ object NonFatal { - - def unapply(t: Throwable): Option[Throwable] = t match { - case e: StackOverflowError ⇒ Some(e) // StackOverflowError ok even though it is a VirtualMachineError - // VirtualMachineError includes OutOfMemoryError and other fatal errors - case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable | _: NotImplementedError => None - case e ⇒ Some(e) - } - + /** + * Returns true if the provided `Throwable` is to be considered non-fatal, or false if it is to be considered fatal + */ + def apply(t: Throwable): Boolean = t match { + case _: StackOverflowError => true // StackOverflowError ok even though it is a VirtualMachineError + // VirtualMachineError includes OutOfMemoryError and other fatal errors + case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable | _: NotImplementedError => false + case _ => true + } + /** + * Returns Some(t) if NonFatal(t) == true, otherwise None + */ + def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None } diff --git a/test/files/jvm/future-spec/FutureTests.scala b/test/files/jvm/future-spec/FutureTests.scala index e5e01a5954..ca9ff5090f 100644 --- a/test/files/jvm/future-spec/FutureTests.scala +++ b/test/files/jvm/future-spec/FutureTests.scala @@ -10,21 +10,69 @@ import scala.runtime.NonLocalReturnControl object FutureTests extends MinimalScalaTest { - + /* some utils */ - def testAsync(s: String): Future[String] = s match { + def testAsync(s: String)(implicit ec: ExecutionContext): Future[String] = s match { case "Hello" => future { "World" } - case "Failure" => Promise.failed(new RuntimeException("Expected exception; to test fault-tolerance")).future + case "Failure" => Future.failed(new RuntimeException("Expected exception; to test fault-tolerance")) case "NoReply" => Promise[String]().future } val defaultTimeout = 5 seconds /* future specification */ + + "A future with custom ExecutionContext" should { + "shouldHandleThrowables" in { + val ms = new mutable.HashSet[Throwable] with mutable.SynchronizedSet[Throwable] + implicit val ec = scala.concurrent.ExecutionContext.fromExecutor(new scala.concurrent.forkjoin.ForkJoinPool(), { + t => + ms += t + }) + + class ThrowableTest(m: String) extends Throwable(m) + + val f1 = future[Any] { + throw new ThrowableTest("test") + } + + intercept[ThrowableTest] { + Await.result(f1, defaultTimeout) + } + + val latch = new TestLatch + val f2 = future { + Await.ready(latch, 5 seconds) + "success" + } + val f3 = f2 map { s => s.toUpperCase } + + f2 foreach { _ => throw new ThrowableTest("dispatcher foreach") } + f2 onSuccess { case _ => throw new ThrowableTest("dispatcher receive") } + + latch.open() + + Await.result(f2, defaultTimeout) mustBe ("success") + + f2 foreach { _ => throw new ThrowableTest("current thread foreach") } + f2 onSuccess { case _ => throw new ThrowableTest("current thread receive") } + + Await.result(f3, defaultTimeout) mustBe ("SUCCESS") + + val waiting = future { + Thread.sleep(1000) + } + Await.ready(waiting, 2000 millis) + + ms.size mustBe (4) + //FIXME should check + } + } - "A future" should { - + "A future with global ExecutionContext" should { + import ExecutionContext.Implicits._ + "compose with for-comprehensions" in { def async(x: Int) = future { (x * 2).toString } val future0 = future[Any] { @@ -122,20 +170,20 @@ object FutureTests extends MinimalScalaTest { val r = new IllegalStateException("recovered") intercept[IllegalStateException] { - val failed = Promise.failed[String](o).future recoverWith { - case _ if false == true => Promise.successful("yay!").future + val failed = Future.failed[String](o) recoverWith { + case _ if false == true => Future.successful("yay!") } Await.result(failed, defaultTimeout) } mustBe (o) - val recovered = Promise.failed[String](o).future recoverWith { - case _ => Promise.successful("yay!").future + val recovered = Future.failed[String](o) recoverWith { + case _ => Future.successful("yay!") } Await.result(recovered, defaultTimeout) mustBe ("yay!") intercept[IllegalStateException] { - val refailed = Promise.failed[String](o).future recoverWith { - case _ => Promise.failed[String](r).future + val refailed = Future.failed[String](o) recoverWith { + case _ => Future.failed[String](r) } Await.result(refailed, defaultTimeout) } mustBe (r) @@ -164,7 +212,7 @@ object FutureTests extends MinimalScalaTest { "firstCompletedOf" in { def futures = Vector.fill[Future[Int]](10) { Promise[Int]().future - } :+ Promise.successful[Int](5).future + } :+ Future.successful[Int](5) Await.result(Future.firstCompletedOf(futures), defaultTimeout) mustBe (5) Await.result(Future.firstCompletedOf(futures.iterator), defaultTimeout) mustBe (5) @@ -186,21 +234,21 @@ object FutureTests extends MinimalScalaTest { val timeout = 10000 millis val f = new IllegalStateException("test") intercept[IllegalStateException] { - val failed = Promise.failed[String](f).future zip Promise.successful("foo").future + val failed = Future.failed[String](f) zip Future.successful("foo") Await.result(failed, timeout) } mustBe (f) intercept[IllegalStateException] { - val failed = Promise.successful("foo").future zip Promise.failed[String](f).future + val failed = Future.successful("foo") zip Future.failed[String](f) Await.result(failed, timeout) } mustBe (f) intercept[IllegalStateException] { - val failed = Promise.failed[String](f).future zip Promise.failed[String](f).future + val failed = Future.failed[String](f) zip Future.failed[String](f) Await.result(failed, timeout) } mustBe (f) - val successful = Promise.successful("foo").future zip Promise.successful("foo").future + val successful = Future.successful("foo") zip Future.successful("foo") Await.result(successful, timeout) mustBe (("foo", "foo")) } @@ -337,50 +385,6 @@ object FutureTests extends MinimalScalaTest { Await.result(traversedIterator, defaultTimeout).sum mustBe (10000) } - "shouldHandleThrowables" in { - val ms = new mutable.HashSet[Throwable] with mutable.SynchronizedSet[Throwable] - implicit val ec = scala.concurrent.ExecutionContext.fromExecutor(new scala.concurrent.forkjoin.ForkJoinPool(), { - t => - ms += t - }) - - class ThrowableTest(m: String) extends Throwable(m) - - val f1 = future[Any] { - throw new ThrowableTest("test") - } - - intercept[ThrowableTest] { - Await.result(f1, defaultTimeout) - } - - val latch = new TestLatch - val f2 = future { - Await.ready(latch, 5 seconds) - "success" - } - val f3 = f2 map { s => s.toUpperCase } - - f2 foreach { _ => throw new ThrowableTest("dispatcher foreach") } - f2 onSuccess { case _ => throw new ThrowableTest("dispatcher receive") } - - latch.open() - - Await.result(f2, defaultTimeout) mustBe ("success") - - f2 foreach { _ => throw new ThrowableTest("current thread foreach") } - f2 onSuccess { case _ => throw new ThrowableTest("current thread receive") } - - Await.result(f3, defaultTimeout) mustBe ("SUCCESS") - - val waiting = future { - Thread.sleep(1000) - } - Await.ready(waiting, 2000 millis) - - ms.size mustBe (4) - } - "shouldBlockUntilResult" in { val latch = new TestLatch diff --git a/test/files/jvm/future-spec/PromiseTests.scala b/test/files/jvm/future-spec/PromiseTests.scala index bf9d9b39d7..49bc642b57 100644 --- a/test/files/jvm/future-spec/PromiseTests.scala +++ b/test/files/jvm/future-spec/PromiseTests.scala @@ -10,7 +10,8 @@ import scala.runtime.NonLocalReturnControl object PromiseTests extends MinimalScalaTest { - + import ExecutionContext.Implicits._ + val defaultTimeout = Inf /* promise specification */ @@ -20,11 +21,13 @@ object PromiseTests extends MinimalScalaTest { "not be completed" in { val p = Promise() p.future.isCompleted mustBe (false) + p.isCompleted mustBe (false) } "have no value" in { val p = Promise() p.future.value mustBe (None) + p.isCompleted mustBe (false) } "return supplied value on timeout" in { @@ -45,14 +48,16 @@ object PromiseTests extends MinimalScalaTest { "A successful Promise" should { val result = "test value" - val future = Promise[String]().complete(Right(result)).future - futureWithResult(_(future, result)) + val promise = Promise[String]().complete(Right(result)) + promise.isCompleted mustBe (true) + futureWithResult(_(promise.future, result)) } "A failed Promise" should { val message = "Expected Exception" - val future = Promise[String]().complete(Left(new RuntimeException(message))).future - futureWithException[RuntimeException](_(future, message)) + val promise = Promise[String]().complete(Left(new RuntimeException(message))) + promise.isCompleted mustBe (true) + futureWithException[RuntimeException](_(promise.future, message)) } "An interrupted Promise" should { diff --git a/test/files/jvm/non-fatal-tests.scala b/test/files/jvm/non-fatal-tests.scala new file mode 100644 index 0000000000..471a9d227a --- /dev/null +++ b/test/files/jvm/non-fatal-tests.scala @@ -0,0 +1,47 @@ +import scala.util.control.NonFatal + +trait NonFatalTests { + + //NonFatals + val nonFatals: Seq[Throwable] = + Seq(new StackOverflowError, + new RuntimeException, + new Exception, + new Throwable) + + //Fatals + val fatals: Seq[Throwable] = + Seq(new InterruptedException, + new OutOfMemoryError, + new LinkageError, + new VirtualMachineError {}, + new Throwable with scala.util.control.ControlThrowable, + new NotImplementedError) + + def testFatalsUsingApply(): Unit = { + fatals foreach { t => assert(NonFatal(t) == false) } + } + + def testNonFatalsUsingApply(): Unit = { + nonFatals foreach { t => assert(NonFatal(t) == true) } + } + + def testFatalsUsingUnapply(): Unit = { + fatals foreach { t => assert(NonFatal.unapply(t).isEmpty) } + } + + def testNonFatalsUsingUnapply(): Unit = { + nonFatals foreach { t => assert(NonFatal.unapply(t).isDefined) } + } + + testFatalsUsingApply() + testNonFatalsUsingApply() + testFatalsUsingUnapply() + testNonFatalsUsingUnapply() +} + +object Test +extends App +with NonFatalTests { + System.exit(0) +} \ No newline at end of file diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala index 407027f904..5c9c71f3f8 100644 --- a/test/files/jvm/scala-concurrent-tck.scala +++ b/test/files/jvm/scala-concurrent-tck.scala @@ -3,22 +3,19 @@ import scala.concurrent.{ Promise, TimeoutException, SyncVar, - ExecutionException + ExecutionException, + ExecutionContext } -import scala.concurrent.future -import scala.concurrent.promise -import scala.concurrent.blocking +import scala.concurrent.{ future, promise, blocking } import scala.util.{ Try, Success, Failure } - import scala.concurrent.util.Duration - trait TestBase { def once(body: (() => Unit) => Unit) { val sv = new SyncVar[Boolean] body(() => sv put true) - sv.take() + sv.take(2000) } // def assert(cond: => Boolean) { @@ -33,7 +30,8 @@ trait TestBase { trait FutureCallbacks extends TestBase { - + import ExecutionContext.Implicits._ + def testOnSuccess(): Unit = once { done => var x = 0 @@ -147,6 +145,7 @@ trait FutureCallbacks extends TestBase { trait FutureCombinators extends TestBase { + import ExecutionContext.Implicits._ def testMapSuccess(): Unit = once { done => @@ -591,7 +590,8 @@ trait FutureCombinators extends TestBase { trait FutureProjections extends TestBase { - + import ExecutionContext.Implicits._ + def testFailedFailureOnComplete(): Unit = once { done => val cause = new RuntimeException @@ -673,7 +673,8 @@ trait FutureProjections extends TestBase { trait Blocking extends TestBase { - + import ExecutionContext.Implicits._ + def testAwaitSuccess(): Unit = once { done => val f = future { 0 } @@ -702,8 +703,67 @@ trait Blocking extends TestBase { } +trait BlockContexts extends TestBase { + import ExecutionContext.Implicits._ + import scala.concurrent.{ Await, Awaitable, BlockContext } + + private def getBlockContext(body: => BlockContext): BlockContext = { + blocking(Future { body }, Duration(500, "ms")) + } + + // test outside of an ExecutionContext + def testDefaultOutsideFuture(): Unit = { + val bc = BlockContext.current + assert(bc.getClass.getName.contains("DefaultBlockContext")) + } + + // test BlockContext in our default ExecutionContext + def testDefaultFJP(): Unit = { + val bc = getBlockContext(BlockContext.current) + assert(bc.isInstanceOf[scala.concurrent.forkjoin.ForkJoinWorkerThread]) + } + + // test BlockContext inside BlockContext.withBlockContext + def testPushCustom(): Unit = { + val orig = BlockContext.current + val customBC = new BlockContext() { + override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = + orig.internalBlockingCall(awaitable, atMost) + } + + val bc = getBlockContext({ + BlockContext.withBlockContext(customBC) { + BlockContext.current + } + }) + + assert(bc eq customBC) + } + + // test BlockContext after a BlockContext.push + def testPopCustom(): Unit = { + val orig = BlockContext.current + val customBC = new BlockContext() { + override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = + orig.internalBlockingCall(awaitable, atMost) + } + + val bc = getBlockContext({ + BlockContext.withBlockContext(customBC) {} + BlockContext.current + }) + + assert(bc ne customBC) + } + + testDefaultOutsideFuture() + testDefaultFJP() + testPushCustom() + testPopCustom() +} trait Promises extends TestBase { + import ExecutionContext.Implicits._ def testSuccess(): Unit = once { done => @@ -730,7 +790,8 @@ trait Promises extends TestBase { trait Exceptions extends TestBase { - + import ExecutionContext.Implicits._ + } // trait TryEitherExtractor extends TestBase { @@ -811,7 +872,7 @@ trait Exceptions extends TestBase { trait CustomExecutionContext extends TestBase { import scala.concurrent.{ ExecutionContext, Awaitable } - def defaultEC = ExecutionContext.defaultExecutionContext + def defaultEC = ExecutionContext.global val inEC = new java.lang.ThreadLocal[Int]() { override def initialValue = 0 @@ -826,7 +887,7 @@ trait CustomExecutionContext extends TestBase { val _count = new java.util.concurrent.atomic.AtomicInteger(0) def count = _count.get - def delegate = ExecutionContext.defaultExecutionContext + def delegate = ExecutionContext.global override def execute(runnable: Runnable) = { _count.incrementAndGet() @@ -843,9 +904,6 @@ trait CustomExecutionContext extends TestBase { delegate.execute(wrapper) } - override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = - delegate.internalBlockingCall(awaitable, atMost) - override def reportFailure(t: Throwable): Unit = { System.err.println("Failure: " + t.getClass.getSimpleName + ": " + t.getMessage) delegate.reportFailure(t) @@ -860,14 +918,16 @@ trait CustomExecutionContext extends TestBase { def testOnSuccessCustomEC(): Unit = { val count = countExecs { implicit ec => - once { done => - val f = future({ assertNoEC() })(defaultEC) - f onSuccess { - case _ => - assertEC() + blocking { + once { done => + val f = future({ assertNoEC() })(defaultEC) + f onSuccess { + case _ => + assertEC() done() + } + assertNoEC() } - assertNoEC() } } @@ -877,12 +937,14 @@ trait CustomExecutionContext extends TestBase { def testKeptPromiseCustomEC(): Unit = { val count = countExecs { implicit ec => - once { done => - val f = Promise.successful(10).future - f onSuccess { - case _ => - assertEC() + blocking { + once { done => + val f = Promise.successful(10).future + f onSuccess { + case _ => + assertEC() done() + } } } } @@ -893,28 +955,30 @@ trait CustomExecutionContext extends TestBase { def testCallbackChainCustomEC(): Unit = { val count = countExecs { implicit ec => - once { done => - assertNoEC() - val addOne = { x: Int => assertEC(); x + 1 } - val f = Promise.successful(10).future - f.map(addOne).filter { x => - assertEC() - x == 11 - } flatMap { x => - Promise.successful(x + 1).future.map(addOne).map(addOne) - } onComplete { - case Left(t) => - try { - throw new AssertionError("error in test: " + t.getMessage, t) - } finally { + blocking { + once { done => + assertNoEC() + val addOne = { x: Int => assertEC(); x + 1 } + val f = Promise.successful(10).future + f.map(addOne).filter { x => + assertEC() + x == 11 + } flatMap { x => + Promise.successful(x + 1).future.map(addOne).map(addOne) + } onComplete { + case Left(t) => + try { + throw new AssertionError("error in test: " + t.getMessage, t) + } finally { + done() + } + case Right(x) => + assertEC() + assert(x == 14) done() - } - case Right(x) => - assertEC() - assert(x == 14) - done() + } + assertNoEC() } - assertNoEC() } } @@ -934,6 +998,7 @@ with FutureCallbacks with FutureCombinators with FutureProjections with Promises +with BlockContexts with Exceptions // with TryEitherExtractor with CustomExecutionContext -- cgit v1.2.3