From 355264f9d53c09182fe6f480319543dc914860d1 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Fri, 13 Apr 2012 11:55:35 +0200 Subject: Scaladoc feature that shows implicit conversions See https://github.com/VladUreche/scala/tree/feature/doc-implicits for the history. See https://scala-webapps.epfl.ch/jenkins/view/scaladoc/job/scaladoc-implicits-nightly/ for nightlies. Many thanks fly out to Adriaan for his help with implicit search! --- src/compiler/scala/reflect/internal/Types.scala | 39 +- src/compiler/scala/tools/ant/Scaladoc.scala | 70 ++- src/compiler/scala/tools/nsc/ast/DocComments.scala | 8 +- src/compiler/scala/tools/nsc/doc/DocFactory.scala | 6 +- src/compiler/scala/tools/nsc/doc/Settings.scala | 127 +++++- .../scala/tools/nsc/doc/Uncompilable.scala | 4 +- .../scala/tools/nsc/doc/html/HtmlFactory.scala | 3 + .../scala/tools/nsc/doc/html/HtmlPage.scala | 12 +- .../scala/tools/nsc/doc/html/page/Template.scala | 156 +++++-- .../nsc/doc/html/resource/lib/conversionbg.gif | Bin 0 -> 167 bytes .../doc/html/resource/lib/selected-implicits.png | Bin 0 -> 1150 bytes .../html/resource/lib/selected-right-implicits.png | Bin 0 -> 646 bytes .../tools/nsc/doc/html/resource/lib/template.css | 101 ++++- .../tools/nsc/doc/html/resource/lib/template.js | 104 +++-- .../scala/tools/nsc/doc/model/Entity.scala | 107 +++++ .../scala/tools/nsc/doc/model/ModelFactory.scala | 99 ++-- .../doc/model/ModelFactoryImplicitSupport.scala | 501 +++++++++++++++++++++ .../scala/tools/nsc/doc/model/TreeFactory.scala | 2 +- .../scala/tools/nsc/typechecker/Implicits.scala | 47 +- .../scala/tools/nsc/typechecker/Infer.scala | 2 +- src/library/scala/Array.scala | 13 + src/library/scala/Option.scala | 11 + src/library/scala/Tuple2.scala | 12 +- src/library/scala/Tuple3.scala | 13 +- .../scala/tools/partest/ScaladocModelTest.scala | 78 ++-- 25 files changed, 1325 insertions(+), 190 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/conversionbg.gif create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-implicits.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-right-implicits.png create mode 100644 src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Types.scala b/src/compiler/scala/reflect/internal/Types.scala index 73a8f5c55c..8bb1d5e2fa 100644 --- a/src/compiler/scala/reflect/internal/Types.scala +++ b/src/compiler/scala/reflect/internal/Types.scala @@ -917,8 +917,8 @@ trait Types extends api.Types { self: SymbolTable => /** A test whether a type contains any unification type variables. */ def isGround: Boolean = this match { - case TypeVar(_, constr) => - constr.instValid && constr.inst.isGround + case tv@TypeVar(_, _) => + tv.untouchable || (tv.instValid && tv.constr.inst.isGround) case TypeRef(pre, sym, args) => sym.isPackageClass || pre.isGround && (args forall (_.isGround)) case SingleType(pre, sym) => @@ -2677,14 +2677,15 @@ trait Types extends api.Types { self: SymbolTable => def unapply(tv: TypeVar): Some[(Type, TypeConstraint)] = Some((tv.origin, tv.constr)) def apply(origin: Type, constr: TypeConstraint): TypeVar = apply(origin, constr, Nil, Nil) def apply(tparam: Symbol): TypeVar = apply(tparam.tpeHK, deriveConstraint(tparam), Nil, tparam.typeParams) + def apply(tparam: Symbol, untouchable: Boolean): TypeVar = apply(tparam.tpeHK, deriveConstraint(tparam), Nil, tparam.typeParams, untouchable) /** This is the only place TypeVars should be instantiated. */ - def apply(origin: Type, constr: TypeConstraint, args: List[Type], params: List[Symbol]): TypeVar = { + def apply(origin: Type, constr: TypeConstraint, args: List[Type], params: List[Symbol], untouchable: Boolean = false): TypeVar = { val tv = ( - if (args.isEmpty && params.isEmpty) new TypeVar(origin, constr) - else if (args.size == params.size) new AppliedTypeVar(origin, constr, params zip args) - else if (args.isEmpty) new HKTypeVar(origin, constr, params) + if (args.isEmpty && params.isEmpty) new TypeVar(origin, constr, untouchable) + else if (args.size == params.size) new AppliedTypeVar(origin, constr, untouchable, params zip args) + else if (args.isEmpty) new HKTypeVar(origin, constr, untouchable, params) else throw new Error("Invalid TypeVar construction: " + ((origin, constr, args, params))) ) @@ -2712,8 +2713,9 @@ trait Types extends api.Types { self: SymbolTable => class HKTypeVar( _origin: Type, _constr: TypeConstraint, + _untouchable: Boolean, override val params: List[Symbol] - ) extends TypeVar(_origin, _constr) { + ) extends TypeVar(_origin, _constr, _untouchable) { require(params.nonEmpty, this) override def isHigherKinded = true @@ -2725,8 +2727,9 @@ trait Types extends api.Types { self: SymbolTable => class AppliedTypeVar( _origin: Type, _constr: TypeConstraint, + _untouchable: Boolean, zippedArgs: List[(Symbol, Type)] - ) extends TypeVar(_origin, _constr) { + ) extends TypeVar(_origin, _constr, _untouchable) { require(zippedArgs.nonEmpty, this) @@ -2749,7 +2752,8 @@ trait Types extends api.Types { self: SymbolTable => */ class TypeVar( val origin: Type, - val constr0: TypeConstraint + val constr0: TypeConstraint, + val untouchable: Boolean = false // by other typevars ) extends Type { override def params: List[Symbol] = Nil override def typeArgs: List[Type] = Nil @@ -2931,14 +2935,15 @@ trait Types extends api.Types { self: SymbolTable => // would be pointless. In this case, each check we perform causes us to lose specificity: in // the end the best we'll do is the least specific type we tested against, since the typevar // does not see these checks as "probes" but as requirements to fulfill. - // TODO: the `suspended` flag can be used to poke around with leaving a trace + // TODO: can the `suspended` flag be used to poke around without leaving a trace? // // So the strategy used here is to test first the type, then the direct parents, and finally // to fall back on the individual base types. This warrants eventual re-examination. // AM: I think we could use the `suspended` flag to avoid side-effecting during unification - if (suspended) // constraint accumulation is disabled + if (tp.isInstanceOf[TypeVar] && untouchable && !tp.asInstanceOf[TypeVar].untouchable) tp.asInstanceOf[TypeVar].registerBound(this, !isLowerBound, isNumericBound) + else if (suspended) // constraint accumulation is disabled checkSubtype(tp, origin) else if (constr.instValid) // type var is already set checkSubtype(tp, constr.inst) @@ -2962,7 +2967,8 @@ trait Types extends api.Types { self: SymbolTable => if(typeVarLHS) constr.inst =:= tp else tp =:= constr.inst - if (suspended) tp =:= origin + if (tp.isInstanceOf[TypeVar] && untouchable && !tp.asInstanceOf[TypeVar].untouchable) tp.asInstanceOf[TypeVar].registerTypeEquality(this, !typeVarLHS) + else if (suspended) tp =:= origin else if (constr.instValid) checkIsSameType(tp) else isRelatable(tp) && { val newInst = wildcardToTypeVarMap(tp) @@ -3036,7 +3042,7 @@ trait Types extends api.Types { self: SymbolTable => override def safeToString = ( if ((constr eq null) || (constr.inst eq null)) "TVar<" + originName + "=null>" else if (constr.inst ne NoType) "" + constr.inst - else "?" + levelString + originName + else (if(untouchable) "!?" else "?") + levelString + originName ) override def kind = "TypeVar" @@ -4733,7 +4739,7 @@ trait Types extends api.Types { self: SymbolTable => val sym1 = adaptToNewRun(sym.owner.thisType, sym) if (sym1 == sym) tp else ThisType(sym1) } catch { - case ex: MissingTypeControl => + case ex: MissingTypeControl => tp } case SingleType(pre, sym) => @@ -6044,8 +6050,9 @@ trait Types extends api.Types { self: SymbolTable => def stripType(tp: Type) = tp match { case ExistentialType(_, res) => res - case TypeVar(_, constr) => - if (constr.instValid) constr.inst + case tv@TypeVar(_, constr) => + if (tv.instValid) constr.inst + else if (tv.untouchable) tv else abort("trying to do lub/glb of typevar "+tp) case t => t } diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index c92474b33e..daa08ef8a7 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -75,6 +75,11 @@ class Scaladoc extends ScalaMatchingTask { */ object Flag extends PermissibleValue { val values = List("yes", "no", "on", "off") + def getBooleanValue(value: String, flagName: String): Boolean = + if (Flag.isPermissible(value)) + return ("yes".equals(value) || "on".equals(value)) + else + buildError("Unknown " + flagName + " flag '" + value + "'") } /** The directories that contain source files to compile. */ @@ -127,6 +132,25 @@ class Scaladoc extends ScalaMatchingTask { /** Instruct the ant task not to fail in the event of errors */ private var nofail: Boolean = false + /** Instruct the scaladoc tool to document implicit conversions */ + private var docImplicits: Boolean = false + + /** Instruct the scaladoc tool to document all (including impossible) implicit conversions */ + private var docImplicitsShowAll: Boolean = false + + /** Instruct the scaladoc tool to output implicits debugging information */ + private var docImplicitsDebug: Boolean = false + + /** Instruct the scaladoc tool to create diagrams */ + private var docDiagrams: Boolean = false + + /** Instruct the scaladoc tool to output diagram creation debugging information */ + private var docDiagramsDebug: Boolean = false + + /** Instruct the scaladoc tool to use the binary given to create diagrams */ + private var docDiagramsDotPath: Option[String] = None + + /*============================================================================*\ ** Properties setters ** \*============================================================================*/ @@ -361,12 +385,39 @@ class Scaladoc extends ScalaMatchingTask { * * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ - def setNoFail(input: String) { - if (Flag.isPermissible(input)) - nofail = "yes".equals(input) || "on".equals(input) - else - buildError("Unknown nofail flag '" + input + "'") - } + def setNoFail(input: String) = + nofail = Flag.getBooleanValue(input, "nofail") + + /** Set the `implicits` info attribute. + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setImplicits(input: String) = + docImplicits = Flag.getBooleanValue(input, "implicits") + + /** Set the `implicitsShowAll` info attribute to enable scaladoc to show all implicits, including those impossible to + * convert to from the default scope + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setImplicitsShowAll(input: String) = + docImplicitsShowAll = Flag.getBooleanValue(input, "implicitsShowAll") + + /** Set the `implicitsDebug` info attribute so scaladoc outputs implicit conversion debug information + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setImplicitsDebug(input: String) = + docImplicitsDebug = Flag.getBooleanValue(input, "implicitsDebug") + + /** Set the `diagrams` bit so Scaladoc adds diagrams to the documentation + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setDiagrams(input: String) = + docDiagrams = Flag.getBooleanValue(input, "diagrams") + + /** Set the `diagramsDebug` bit so Scaladoc outputs diagram building debug information + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setDiagramsDebug(input: String) = + docDiagramsDebug = Flag.getBooleanValue(input, "diagramsDebug") + + /** Set the `diagramsDotPath` attribute to the path where graphviz dot can be found (including the binary file name, + * eg: /usr/bin/dot) */ + def setDiagramsDotPath(input: String) = + docDiagramsDotPath = Some(input) /*============================================================================*\ ** Properties getters ** @@ -560,6 +611,13 @@ class Scaladoc extends ScalaMatchingTask { docSettings.deprecation.value = deprecation docSettings.unchecked.value = unchecked + docSettings.docImplicits.value = docImplicits + docSettings.docImplicitsDebug.value = docImplicitsDebug + docSettings.docImplicitsShowAll.value = docImplicitsShowAll + docSettings.docDiagrams.value = docDiagrams + docSettings.docDiagramsDebug.value = docDiagramsDebug + if(!docDiagramsDotPath.isEmpty) docSettings.docDiagramsDotPath.value = docDiagramsDotPath.get + if (!docgenerator.isEmpty) docSettings.docgenerator.value = docgenerator.get if (!docrootcontent.isEmpty) docSettings.docRootContent.value = docrootcontent.get.getAbsolutePath() log("Scaladoc params = '" + addParams + "'", Project.MSG_DEBUG) diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index ff4e2f3fb5..8e7eeed3cc 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -252,7 +252,7 @@ trait DocComments { self: Global => def replaceInheritdoc(childSection: String, parentSection: => String) = if (childSection.indexOf("@inheritdoc") == -1) childSection - else + else childSection.replaceAllLiterally("@inheritdoc", parentSection) def getParentSection(section: (Int, Int)): String = { @@ -275,9 +275,9 @@ trait DocComments { self: Global => } child.substring(section._1, section._1 + 7) match { - case param@("@param "|"@tparam"|"@throws") => + case param@("@param "|"@tparam"|"@throws") => sectionString(extractSectionParam(child, section), parentNamedParams(param.trim)) - case _ => + case _ => sectionString(extractSectionTag(child, section), parentTagMap) } } @@ -367,7 +367,7 @@ trait DocComments { self: Global => case vname => lookupVariable(vname, site) match { case Some(replacement) => replaceWith(replacement) - case None => reporter.warning(sym.pos, "Variable " + vname + " undefined in comment for " + sym) + case None => reporter.warning(sym.pos, "Variable " + vname + " undefined in comment for " + sym + " in " + site) } } } diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index f32564f097..76a8b87ba7 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -58,7 +58,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor case Right(sourceCode) => new compiler.Run() compileSources List(new BatchSourceFile("newSource", sourceCode)) } - + if (reporter.hasErrors) return None @@ -80,6 +80,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor val modelFactory = ( new { override val global: compiler.type = compiler } with model.ModelFactory(compiler, settings) + with model.ModelFactoryImplicitSupport with model.comment.CommentFactory with model.TreeFactory { override def templateShouldDocument(sym: compiler.Symbol) = @@ -89,7 +90,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor modelFactory.makeModel match { case Some(madeModel) => - println("model contains " + modelFactory.templatesCount + " documentable templates") + if (settings.reportModel) + println("model contains " + modelFactory.templatesCount + " documentable templates") Some(madeModel) case None => println("no documentable class found in compilation units") diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 45a2ad78b4..3da87bf763 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -87,6 +87,38 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { "" ) + val docImplicits = BooleanSetting ( + "-implicits", + "Document members inherited by implicit conversions." + ) + + val docImplicitsDebug = BooleanSetting ( + "-implicits-debug", + "Show debugging information for members inherited by implicit conversions." + ) + + val docImplicitsShowAll = BooleanSetting ( + "-implicits-show-all", + "Show members inherited by implicit conversions that are impossible in the default scope. " + + "(for example conversions that require Numeric[String] to be in scope)" + ) + + val docDiagrams = BooleanSetting ( + "-diagrams", + "Create inheritance diagrams for classes, traits and packages." + ) + + val docDiagramsDebug = BooleanSetting ( + "-diagrams-debug", + "Show debugging information for the diagram creation process." + ) + + val docDiagramsDotPath = PathSetting ( + "-diagrams-dot-path", + "The path to the dot executable used to generate the inheritance diagrams. Ex: /usr/bin/dot", + "dot" // by default, just pick up the system-wide dot + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -94,9 +126,102 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { // For improved help output. def scaladocSpecific = Set[Settings#Setting]( - docformat, doctitle, docfooter, docversion, docUncompilable, docsourceurl, docgenerator + docformat, doctitle, docfooter, docversion, docUncompilable, docsourceurl, docgenerator, docRootContent, useStupidTypes, + docDiagrams, docDiagramsDebug, docDiagramsDotPath, + docImplicits, docImplicitsDebug, docImplicitsShowAll ) 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 + + /** + * This is the hardcoded area of Scaladoc. This is where "undesirable" stuff gets eliminated. I know it's not pretty, + * but ultimately scaladoc has to be useful. :) + */ + object hardcoded { + + /** The common context bounds and some humanly explanations. Feel free to add more explanations + * `.scala.package.Numeric` is the type class + * `tparam` is the name of the type parameter it gets (this only describes type classes with 1 type param) + * the function result should be a humanly-understandable description of the type class + */ + val knownTypeClasses: Map[String, String => String] = Map() + + (".scala.package.Numeric" -> ((tparam: String) => tparam + " is a numeric class, such as Int, Long, Float or Double")) + + (".scala.package.Integral" -> ((tparam: String) => tparam + " is an integral numeric class, such as Int or Long")) + + (".scala.package.Fractional" -> ((tparam: String) => tparam + " is a fractional numeric class, such as Float or Double")) + + (".scala.reflect.Manifest" -> ((tparam: String) => tparam + " is accompanied by a Manifest, which is a runtime representation of its type that survives erasure")) + + (".scala.reflect.ClassManifest" -> ((tparam: String) => tparam + " is accompanied by a ClassManifest, which is a runtime representation of its type that survives erasure")) + + (".scala.reflect.OptManifest" -> ((tparam: String) => tparam + " is accompanied by an OptManifest, which can be either a runtime representation of its type or the NoManifest, which means the runtime type is not available")) + + /** + * Set of classes to exclude from index and diagrams + * TODO: Should be configurable + */ + def isExcluded(qname: String) = { + ( ( 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" + ) + ) + } + + /** Common conversion targets that affect any class in Scala */ + val commonConversionTargets = List( + "scala.Predef.any2stringfmt", + "scala.Predef.any2stringadd", + "scala.Predef.any2ArrowAssoc", + "scala.Predef.any2Ensuring") + + /** There's a reason all these are specialized by hand but documenting each of them is beyond the point */ + val arraySkipConversions = List( + "scala.Predef.refArrayOps", + "scala.Predef.intArrayOps", + "scala.Predef.doubleArrayOps", + "scala.Predef.longArrayOps", + "scala.Predef.floatArrayOps", + "scala.Predef.charArrayOps", + "scala.Predef.byteArrayOps", + "scala.Predef.shortArrayOps", + "scala.Predef.booleanArrayOps", + "scala.Predef.unitArrayOps", + "scala.LowPriorityImplicits.wrapRefArray", + "scala.LowPriorityImplicits.wrapIntArray", + "scala.LowPriorityImplicits.wrapDoubleArray", + "scala.LowPriorityImplicits.wrapLongArray", + "scala.LowPriorityImplicits.wrapFloatArray", + "scala.LowPriorityImplicits.wrapCharArray", + "scala.LowPriorityImplicits.wrapByteArray", + "scala.LowPriorityImplicits.wrapShortArray", + "scala.LowPriorityImplicits.wrapBooleanArray", + "scala.LowPriorityImplicits.wrapUnitArray", + "scala.LowPriorityImplicits.genericWrapArray") + + // included as names as here we don't have access to a Global with Definitions :( + def valueClassList = List("unit", "boolean", "byte", "short", "char", "int", "long", "float", "double") + def valueClassFilterPrefixes = List("scala.LowPriorityImplicits", "scala.Predef") + + /** Dirty, dirty, dirty hack: the value params conversions can all kick in -- and they are disambiguated by priority + * but showing priority in scaladoc would make no sense -- so we have to manually remove the conversions that we + * know will never get a chance to kick in. Anyway, DIRTY DIRTY DIRTY! */ + def valueClassFilter(value: String, conversionName: String): Boolean = { + val valueName = value.toLowerCase + val otherValues = valueClassList.filterNot(_ == valueName) + + for (prefix <- valueClassFilterPrefixes) + if (conversionName.startsWith(prefix)) + for (otherValue <- otherValues) + if (conversionName.startsWith(prefix + "." + otherValue)) + return false + + true + } + } } diff --git a/src/compiler/scala/tools/nsc/doc/Uncompilable.scala b/src/compiler/scala/tools/nsc/doc/Uncompilable.scala index 9b29ebd745..dbeca46c25 100644 --- a/src/compiler/scala/tools/nsc/doc/Uncompilable.scala +++ b/src/compiler/scala/tools/nsc/doc/Uncompilable.scala @@ -14,7 +14,7 @@ trait Uncompilable { val settings: Settings import global.{ reporter, inform, warning, newTypeName, newTermName, Symbol, Name, DocComment, NoSymbol } - import global.definitions.RootClass + import global.definitions.{ RootClass, AnyRefClass } private implicit def translateName(name: Global#Name) = if (name.isTypeName) newTypeName("" + name) else newTermName("" + name) @@ -32,7 +32,7 @@ trait Uncompilable { } def files = settings.uncompilableFiles def symbols = pairs map (_._1) - def templates = symbols filter (x => x.isClass || x.isTrait) toSet + def templates = symbols filter (x => x.isClass || x.isTrait || x == AnyRefClass/* which is now a type alias */) toSet def comments = { if (settings.debug.value || settings.verbose.value) inform("Found %d uncompilable files: %s".format(files.size, files mkString ", ")) diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala index 0116e02e0e..914824d523 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -71,6 +71,7 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { "signaturebg.gif", "signaturebg2.gif", "typebg.gif", + "conversionbg.gif", "valuemembersbg.gif", "navigation-li-a.png", @@ -80,6 +81,8 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { "selected.png", "selected2-right.png", "selected2.png", + "selected-right-implicits.png", + "selected-implicits.png", "unselected.png" ) diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index 1544dafc69..e3da8bddea 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -23,7 +23,7 @@ abstract class HtmlPage extends Page { thisPage => protected def title: String /** The page description */ - protected def description: String = + protected def description: String = // unless overwritten, will display the title in a spaced format, keeping - and . title.replaceAll("[^a-zA-Z0-9\\.\\-]+", " ").replaceAll("\\-+", " - ").replaceAll(" +", " ") @@ -164,15 +164,15 @@ abstract class HtmlPage extends Page { thisPage => } /** Returns the HTML code that represents the template in `tpl` as a hyperlinked name. */ - def templateToHtml(tpl: TemplateEntity) = tpl match { + def templateToHtml(tpl: TemplateEntity, name: String = null) = tpl match { case dTpl: DocTemplateEntity => if (hasPage(dTpl)) { - { dTpl.name } + { if (name eq null) dTpl.name else name } } else { - xml.Text(dTpl.name) + xml.Text(if (name eq null) dTpl.name else name) } case ndTpl: NoDocTemplate => - xml.Text(ndTpl.name) + xml.Text(if (name eq null) ndTpl.name else name) } /** Returns the HTML code that represents the templates in `tpls` as a list of hyperlinked names. */ @@ -192,6 +192,6 @@ abstract class HtmlPage extends Page { thisPage => else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isTrait) "object_to_trait_big.png" else if (ety.isObject) "object_big.png" else if (ety.isPackage) "package_big.png" - else "class_big.png" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not + else "class_big.png" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index f059b5c0cb..680957c64c 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -88,21 +88,42 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
- { if (tpl.linearizationTemplates.isEmpty) NodeSeq.Empty else + { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) NodeSeq.Empty else
Ordering
  1. Alphabetic
  2. By inheritance
} - { if (tpl.linearizationTemplates.isEmpty) NodeSeq.Empty else -
- Inherited -
  1. Hide All
  2. -
  3. Show all
-
    { - (tpl :: tpl.linearizationTemplates) map { wte =>
  1. { wte.name }
  2. } - }
-
+ { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) NodeSeq.Empty else + { + if (!tpl.linearization.isEmpty) +
+ Inherited
+
+
    + { (tpl :: tpl.linearizationTemplates).map(wte =>
  1. { wte.name }
  2. ) } +
+
+ else NodeSeq.Empty + } ++ { + if (!tpl.conversions.isEmpty) +
+ Implicitly
+
+
    + { tpl.conversions.map(conv =>
  1. { "by " + conv.conversionShortName }
  2. ) } +
+
+ else NodeSeq.Empty + } ++ +
+ +
    +
  1. Hide All
  2. +
  3. Show all
  4. +
+ Learn more about member selection +
} {
@@ -152,23 +173,25 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
{ + // linearization NodeSeq fromSeq (for ((superTpl, superType) <- (tpl.linearizationTemplates zip tpl.linearizationTypes)) yield

Inherited from { - if (tpl.universe.settings.useStupidTypes.value) - superTpl match { - case dtpl: DocTemplateEntity => - val sig = signature(dtpl, false, true) \ "_" - sig - case tpl: TemplateEntity => - tpl.name - } - else - typeToHtml(superType, true) + typeToHtmlWithStupidTypes(tpl, superTpl, superType) }

) } + { + // implicitly inherited + NodeSeq fromSeq (for (conversion <- (tpl.conversions)) yield +
+

Inherited by implicit conversion { conversion.conversionShortName } from + { typeToHtml(tpl.resultType, true) } to { typeToHtml(conversion.targetType, true) } +

+
+ ) + }
@@ -219,11 +242,12 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage case d:MemberEntity with Def => defParamsToString(d) case _ => "" } + val memberComment = memberToCommentHtml(mbr, false)
  • + data-isabs={ mbr.isAbstract.toString } fullComment={ if(memberComment.isEmpty) "no" else "yes" }> { signature(mbr, false) } - { memberToCommentHtml(mbr, false) } + { memberComment }
  • } @@ -275,6 +299,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage

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

    def memberToCommentBodyHtml(mbr: MemberEntity, isSelf: Boolean, isReduced: Boolean = false): NodeSeq = { + val memberComment = if (mbr.comment.isEmpty) NodeSeq.Empty else
    { commentToHtml(mbr.comment) }
    @@ -326,6 +351,45 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } } + val implicitInformation = mbr.byConversion match { + case Some(conv) => +
    Implicit information
    ++ + { + val targetType = typeToHtml(conv.targetType, true) + val conversionMethod = conv.convertorMethod match { + case Left(member) => Text(member.name) + case Right(name) => Text(name) + } + + // strip off the package object endings, they make things harder to follow + val conversionOwnerQualifiedNane = conv.convertorOwner.qualifiedName.stripSuffix(".package") + val conversionOwner = templateToHtml(conv.convertorOwner, conversionOwnerQualifiedNane) + + val constraintText = conv.constraints match { + case Nil => + NodeSeq.Empty + case List(constraint) => + xml.Text("This conversion will take place only if ") ++ constraintToHtml(constraint) ++ xml.Text(".") + case List(constraint1, constraint2) => + xml.Text("This conversion will take place only if ") ++ constraintToHtml(constraint1) ++ + xml.Text(" and at the same time ") ++ constraintToHtml(constraint2) ++ xml.Text(".") + case constraints => +
    ++ "This conversion will take place only if all of the following constraints are met:" ++
    ++ { + var index = 0 + constraints map { constraint => xml.Text({ index += 1; index } + ". ") ++ constraintToHtml(constraint) ++
    } + } + } + +
    + This member is added by an implicit conversion from { typeToHtml(mbr.inTemplate.resultType, true) } to + { targetType } performed by method { conversionMethod } in { conversionOwner }. + { constraintText } +
    + } + case _ => + NodeSeq.Empty + } + // --- start attributes block vals val attributes: Seq[scala.xml.Node] = { val fvs: List[comment.Paragraph] = visibility(mbr).toList @@ -354,7 +418,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
    case _ => NodeSeq.Empty } - } + } val selfType: Seq[scala.xml.Node] = mbr match { case dtpl: DocTemplateEntity if (isSelf && !dtpl.selfType.isEmpty && !isReduced) => @@ -477,7 +541,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage } // end attributes block vals --- - val attributesInfo = attributes ++ definitionClasses ++ fullSignature ++ selfType ++ annotations ++ deprecation ++ migration ++ sourceLink ++ mainComment + val attributesInfo = implicitInformation ++ attributes ++ definitionClasses ++ fullSignature ++ selfType ++ annotations ++ deprecation ++ migration ++ sourceLink ++ mainComment val attributesBlock = if (attributesInfo.isEmpty) NodeSeq.Empty @@ -561,12 +625,13 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { + val nameClass = if (mbr.byConversion.isDefined) "implicit" else "name" val nameHtml = { val value = if (mbr.isConstructor) tpl.name else mbr.name val span = if (mbr.deprecation.isDefined) - { value } + { value } else - { value } + { value } val encoded = scala.reflect.NameTransformer.encode(value) if (encoded != value) { span % new UnprefixedAttribute("title", @@ -765,4 +830,43 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage case _ => inl.toString } + private def typeToHtmlWithStupidTypes(tpl: TemplateEntity, superTpl: TemplateEntity, superType: TypeEntity): NodeSeq = + if (tpl.universe.settings.useStupidTypes.value) + superTpl match { + case dtpl: DocTemplateEntity => + val sig = signature(dtpl, false, true) \ "_" + sig + case tpl: TemplateEntity => + Text(tpl.name) + } + else + typeToHtml(superType, true) + + private def constraintToHtml(constraint: Constraint): NodeSeq = constraint match { + case ktcc: KnownTypeClassConstraint => + xml.Text(ktcc.typeExplanation(ktcc.typeParamName) + " (" + ktcc.typeParamName + ": ") ++ + templateToHtml(ktcc.typeClassEntity) ++ xml.Text(")") + case tcc: TypeClassConstraint => + xml.Text(tcc.typeParamName + " is ") ++ + + context-bounded ++ xml.Text(" by " + tcc.typeClassEntity.qualifiedName + " (" + tcc.typeParamName + ": ") ++ + templateToHtml(tcc.typeClassEntity) ++ xml.Text(")") + case impl: ImplicitInScopeConstraint => + xml.Text("an implicit value of type ") ++ typeToHtml(impl.implicitType, true) ++ xml.Text(" is in scope") + case eq: EqualTypeParamConstraint => + xml.Text(eq.typeParamName + " is " + eq.rhs.name + " (" + eq.typeParamName + " =:= ") ++ + typeToHtml(eq.rhs, true) ++ xml.Text(")") + case bt: BoundedTypeParamConstraint => + xml.Text(bt.typeParamName + " is a superclass of " + bt.lowerBound.name + " and a subclass of " + + bt.upperBound.name + " (" + bt.typeParamName + " >: ") ++ + typeToHtml(bt.lowerBound, true) ++ xml.Text(" <: ") ++ + typeToHtml(bt.upperBound, true) ++ xml.Text(")") + case lb: LowerBoundedTypeParamConstraint => + xml.Text(lb.typeParamName + " is a superclass of " + lb.lowerBound.name + " (" + lb.typeParamName + " >: ") ++ + typeToHtml(lb.lowerBound, true) ++ xml.Text(")") + case ub: UpperBoundedTypeParamConstraint => + xml.Text(ub.typeParamName + " is a subclass of " + ub.upperBound.name + " (" + ub.typeParamName + " <: ") ++ + typeToHtml(ub.upperBound, true) ++ xml.Text(")") + } + } diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/conversionbg.gif b/src/compiler/scala/tools/nsc/doc/html/resource/lib/conversionbg.gif new file mode 100644 index 0000000000..4be145d0af Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/conversionbg.gif differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-implicits.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-implicits.png new file mode 100644 index 0000000000..bc29efb3e6 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-implicits.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-right-implicits.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-right-implicits.png new file mode 100644 index 0000000000..8313f4975b Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/selected-right-implicits.png differ 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 6fb83c133e..5a1779bba5 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 @@ -106,7 +106,7 @@ a[href]:hover { font-size: 24pt; text-shadow: black 0px 2px 0px; /* text-shadow: black 0px 0px 0px;*/ -text-decoration: none; +text-decoration: none; } #definition #owner { @@ -162,7 +162,7 @@ text-decoration: none; padding-left: 15px; background: url("arrow-right.png") no-repeat 0 3px transparent; } - + .toggleContainer.open .toggle { background: url("arrow-down.png") no-repeat 0 3px transparent; } @@ -205,6 +205,11 @@ dl.attributes > dt { font-style: italic; } +dl.attributes > dt.implicit { + font-weight: bold; + color: darkgreen; +} + dl.attributes > dd { display: block; padding-left: 10em; @@ -241,6 +246,17 @@ dl.attributes > dd { color: white; } +#inheritedMembers > div.conversion > h3 { + background: #dadada url("conversionbg.gif") repeat-x bottom left; /* gray */ + height: 17px; + font-style: italic; + font-size: 12pt; +} + +#inheritedMembers > div.conversion > h3 * { + color: white; +} + /* Member cells */ div.members > ol { @@ -310,10 +326,21 @@ div.members > ol > li:last-child { font-weight: bold; } -.signature .symbol .params .implicit { +.signature .symbol > .implicit { + display: inline-block; + font-weight: bold; + text-decoration: underline; + color: darkgreen; +} + +.signature .symbol .params > .implicit { font-style: italic; } +.signature .symbol .implicit.deprecated { + text-decoration: line-through; +} + .signature .symbol .name.deprecated { text-decoration: line-through; } @@ -369,15 +396,15 @@ div.members > ol > li:last-child { .cmt {} .cmt p { - margin: 0.7em 0; + margin: 0.7em 0; } .cmt p:first-child { - margin-top: 0; + margin-top: 0; } .cmt p:last-child { - margin-bottom: 0; + margin-bottom: 0; } .cmt h3, @@ -539,7 +566,7 @@ div.fullcommenttop .block { margin-bottom: 5px } -div.fullcomment div.block ol li p, +div.fullcomment div.block ol li p, div.fullcomment div.block ol li { display:inline } @@ -583,10 +610,10 @@ div.fullcomment dl.paramcmts > dd { /* Members filter tool */ #textfilter { - position: relative; - display: block; + position: relative; + display: block; height: 20px; - margin-bottom: 5px; + margin-bottom: 5px; } #textfilter > .pre { @@ -600,7 +627,7 @@ div.fullcomment dl.paramcmts > dd { } #textfilter > .input { - display: block; + display: block; position: absolute; top: 0; right: 20px; @@ -608,10 +635,10 @@ div.fullcomment dl.paramcmts > dd { } #textfilter > .input > input { - height: 20px; - padding: 1px; - font-weight: bold; - color: #000000; + height: 20px; + padding: 1px; + font-weight: bold; + color: #000000; background: #ffffff url("filterboxbarbg.png") repeat-x top left; width: 100%; } @@ -660,6 +687,13 @@ div.fullcomment dl.paramcmts > dd { display: inline-block; } +#mbrsel > div > a { + position:relative; + top: -8px; + font-size: 11px; + text-shadow: #ffffff 0 1px 0; +} + #mbrsel > div > ol#linearization { display: table; margin-left: 70px; @@ -683,9 +717,32 @@ div.fullcomment dl.paramcmts > dd { text-shadow: #ffffff 0 1px 0; } +#mbrsel > div > ol#implicits { + display: table; + margin-left: 70px; +} + +#mbrsel > div > ol#implicits > li.in { + text-decoration: none; + float: left; + padding-right: 10px; + margin-right: 5px; + background: url(selected-right-implicits.png) no-repeat; + background-position: right 0px; +} + +#mbrsel > div > ol#implicits > li.in > span{ + color: #404040; + float: left; + padding: 1px 0 1px 10px; + background: url(selected-implicits.png) no-repeat; + background-position: 0px 0px; + text-shadow: #ffffff 0 1px 0; +} + #mbrsel > div > ol > li { /* padding: 3px 10px;*/ - line-height: 16pt; + line-height: 16pt; display: inline-block; cursor: pointer; } @@ -709,10 +766,10 @@ div.fullcomment dl.paramcmts > dd { } #mbrsel > div > ol > li.out { - text-decoration: none; - float: left; - padding-right: 10px; - margin-right: 5px; + text-decoration: none; + float: left; + padding-right: 10px; + margin-right: 5px; } #mbrsel > div > ol > li.out > span{ @@ -739,10 +796,10 @@ div.fullcomment dl.paramcmts > dd { #mbrsel .showall { color: #4C4C4C; line-height: 16px; - font-weight: bold; + font-weight: bold; } #mbrsel .showall span { color: #4C4C4C; - font-weight: bold; + font-weight: bold; }*/ \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js index 3cdd9a7f27..fd5a981cb0 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js @@ -2,21 +2,23 @@ // code by Gilles Dubochet with contributions by Pedro Furlanetto $(document).ready(function(){ - var isHiddenClass; - if (document.title == 'scala.AnyRef') { - isHiddenClass = function (name) { - return name == 'scala.Any'; - }; - } else { - isHiddenClass = function (name) { - return name == 'scala.Any' || name == 'scala.AnyRef'; - }; - } + var isHiddenClass = function (name) { + return name == 'scala.Any' || + name == 'scala.AnyRef' || + name == 'scala.Predef.any2stringfmt' || + name == 'scala.Predef.any2stringadd' || + name == 'scala.Predef.any2ArrowAssoc' || + name == 'scala.Predef.any2Ensuring' + }; + + $("#linearization li:gt(0)").filter(function(){ + return isHiddenClass($(this).attr("name")); + }).removeClass("in").addClass("out"); - $("#linearization li").filter(function(){ + $("#implicits li").filter(function(){ return isHiddenClass($(this).attr("name")); }).removeClass("in").addClass("out"); - + // Pre-filter members filter(); @@ -54,17 +56,38 @@ $(document).ready(function(){ }; filter(); }); - $("#ancestors > ol > li.hideall").click(function() { + + $("#implicits li").click(function(){ + if ($(this).hasClass("in")) { + $(this).removeClass("in"); + $(this).addClass("out"); + } + else if ($(this).hasClass("out")) { + $(this).removeClass("out"); + $(this).addClass("in"); + }; + filter(); + }); + + $("#mbrsel > div[id=ancestors] > ol > li.hideall").click(function() { $("#linearization li.in").removeClass("in").addClass("out"); $("#linearization li:first").removeClass("out").addClass("in"); + $("#implicits li.in").removeClass("in").addClass("out"); filter(); }) - $("#ancestors > ol > li.showall").click(function() { - var filtered = + $("#mbrsel > div[id=ancestors] > ol > li.showall").click(function() { + var filteredLinearization = $("#linearization li.out").filter(function() { return ! isHiddenClass($(this).attr("name")); }); - filtered.removeClass("out").addClass("in"); + filteredLinearization.removeClass("out").addClass("in"); + + var filteredImplicits = + $("#implicits li.out").filter(function() { + return ! isHiddenClass($(this).attr("name")); + }); + filteredImplicits.removeClass("out").addClass("in"); + filter(); }); $("#visbl > ol > li.public").click(function() { @@ -108,8 +131,10 @@ $(document).ready(function(){ }); /* Add toggle arrows */ - var docAllSigs = $("#template li").has(".fullcomment").find(".signature"); - + //var docAllSigs = $("#template li").has(".fullcomment").find(".signature"); + // trying to speed things up a little bit + var docAllSigs = $("#template li[fullComment=yes] .signature"); + function commentToggleFct(signature){ var parent = signature.parent(); var shortComment = $(".shortcomment", parent); @@ -129,7 +154,7 @@ $(document).ready(function(){ docAllSigs.click(function() { commentToggleFct($(this)); }); - + /* Linear super types and known subclasses */ function toggleShowContentFct(outerElement){ var content = $(".hiddenContent", outerElement); @@ -148,20 +173,22 @@ $(document).ready(function(){ $(".toggleContainer").click(function() { toggleShowContentFct($(this)); }); - + // Set parent window title windowTitle(); }); function orderAlpha() { $("#template > div.parent").hide(); - $("#ancestors").show(); + $("#template > div.conversion").hide(); + $("#mbrsel > div[id=ancestors]").show(); filter(); }; function orderInherit() { $("#template > div.parent").show(); - $("#ancestors").hide(); + $("#template > div.conversion").show(); + $("#mbrsel > div[id=ancestors]").hide(); filter(); }; @@ -177,6 +204,9 @@ function initInherit() { $("#inheritedMembers > div.parent").each(function(){ parents[$(this).attr("name")] = $(this); }); + $("#inheritedMembers > div.conversion").each(function(){ + parents[$(this).attr("name")] = $(this); + }); $("#types > ol > li").each(function(){ var mbr = $(this); this.mbrText = mbr.find("> .fullcomment .cmt").text(); @@ -216,6 +246,9 @@ function initInherit() { $("#inheritedMembers > div.parent").each(function() { if ($("> div.members", this).length == 0) { $(this).remove(); }; }); + $("#inheritedMembers > div.conversion").each(function() { + if ($("> div.members", this).length == 0) { $(this).remove(); }; + }); }; function filter(scrollToMember) { @@ -224,13 +257,17 @@ function filter(scrollToMember) { var queryRegExp = new RegExp(query, "i"); var privateMembersHidden = $("#visbl > ol > li.public").hasClass("in"); var orderingAlphabetic = $("#order > ol > li.alpha").hasClass("in"); - var hiddenSuperclassElements = orderingAlphabetic ? $("#linearization > li.out") : $("#linearization > li:gt(0)"); - var hiddenSuperclasses = hiddenSuperclassElements.map(function() { + var hiddenSuperclassElementsLinearization = orderingAlphabetic ? $("#linearization > li.out") : $("#linearization > li:gt(0)"); + var hiddenSuperclassesLinearization = hiddenSuperclassElementsLinearization.map(function() { + return $(this).attr("name"); + }).get(); + var hiddenSuperclassElementsImplicits = orderingAlphabetic ? $("#implicits > li.out") : $("#implicits > li"); + var hiddenSuperclassesImplicits = hiddenSuperclassElementsImplicits.map(function() { return $(this).attr("name"); }).get(); var hideInheritedMembers; - + if(orderingAlphabetic) { $("#inheritedMembers").hide(); hideInheritedMembers = true; @@ -242,9 +279,10 @@ function filter(scrollToMember) { $("#allMembers > .members").each(filterFunc); hideInheritedMembers = false; $("#inheritedMembers > .parent > .members").each(filterFunc); + $("#inheritedMembers > .conversion > .members").each(filterFunc); } - + function filterFunc() { var membersVisible = false; var members = $(this); @@ -262,12 +300,18 @@ function filter(scrollToMember) { ownerIndex = name.lastIndexOf("."); } var owner = name.slice(0, ownerIndex); - for (var i = 0; i < hiddenSuperclasses.length; i++) { - if (hiddenSuperclasses[i] == owner) { + for (var i = 0; i < hiddenSuperclassesLinearization.length; i++) { + if (hiddenSuperclassesLinearization[i] == owner) { mbr.hide(); return; } - } + }; + for (var i = 0; i < hiddenSuperclassesImplicits.length; i++) { + if (hiddenSuperclassesImplicits[i] == owner) { + mbr.hide(); + return; + } + }; } if (query && !(queryRegExp.test(name) || queryRegExp.test(this.mbrText))) { mbr.hide(); @@ -276,7 +320,7 @@ function filter(scrollToMember) { mbr.show(); membersVisible = true; }); - + if (membersVisible) members.show(); else diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 6eb14a4907..6488847049 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -167,6 +167,8 @@ trait MemberEntity extends Entity { /** Whether this member is abstract. */ def isAbstract: Boolean + /** If this member originates from an implicit conversion, we set the implicit information to the correct origin */ + def byConversion: Option[ImplicitConversion] } object MemberEntity { // Oh contravariance, contravariance, wherefore art thou contravariance? @@ -246,6 +248,8 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { * other entity of the pair is the companion. */ def companion: Option[DocTemplateEntity] + /** The implicit conversions this template (class or trait, objects and packages are not affected) */ + def conversions: List[ImplicitConversion] } @@ -413,3 +417,106 @@ trait Annotation extends Entity { def arguments: List[ValueArgument] } + +/** A trait that signals the member results from an implicit conversion */ +trait ImplicitConversion { + + /** The source of the implicit conversion*/ + def source: DocTemplateEntity + + /** The result type after the conversion */ + def targetType: TypeEntity + + /** The entity for the method that performed the conversion, if it's documented (or just its name, otherwise) */ + def convertorMethod: Either[MemberEntity, String] + + /** A short name of the convertion */ + def conversionShortName: String + + /** A qualified name uniquely identifying the convertion (currently: the conversion method's qualified name) */ + def conversionQualifiedName: String + + /** The entity that performed the conversion */ + def convertorOwner: TemplateEntity + + /** The constraints that the transformations puts on the type parameters */ + def constraints: List[Constraint] + + /** The members inherited by this implicit conversion */ + 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 +} + +/** A constraint involving a type parameter which must be in scope */ +trait ImplicitInScopeConstraint extends Constraint { + /** The type of the implicit value required */ + def implicitType: TypeEntity + + /** toString for debugging */ + override def toString = "an implicit _: " + implicitType.name + " must be in scope" +} + +trait TypeClassConstraint extends ImplicitInScopeConstraint with TypeParamConstraint { + /** Type class name */ + def typeClassEntity: TemplateEntity + + /** toString for debugging */ + override def toString = typeParamName + " is a class of type " + typeClassEntity.qualifiedName + " (" + + typeParamName + ": " + typeClassEntity.name + ")" +} + +trait KnownTypeClassConstraint extends TypeClassConstraint { + /** Type explanation, takes the type parameter name and generates the explanation */ + def typeExplanation: (String) => String + + /** toString for debugging */ + override def toString = typeExplanation(typeParamName) + " (" + typeParamName + ": " + typeClassEntity.name + ")" +} + +/** A constraint involving a type parameter */ +trait TypeParamConstraint extends Constraint { + /** The type parameter involved */ + def typeParamName: String +} + +trait EqualTypeParamConstraint extends TypeParamConstraint { + /** The rhs */ + def rhs: TypeEntity + /** toString for debugging */ + override def toString = typeParamName + " is " + rhs.name + " (" + typeParamName + " =:= " + rhs.name + ")" +} + +trait BoundedTypeParamConstraint extends TypeParamConstraint { + /** The lower bound */ + def lowerBound: TypeEntity + + /** The upper bound */ + def upperBound: TypeEntity + + /** toString for debugging */ + override def toString = typeParamName + " is a superclass of " + lowerBound.name + " and a subclass of " + + upperBound.name + " (" + typeParamName + " >: " + lowerBound.name + " <: " + upperBound.name + ")" +} + +trait LowerBoundedTypeParamConstraint extends TypeParamConstraint { + /** The lower bound */ + def lowerBound: TypeEntity + + /** toString for debugging */ + override def toString = typeParamName + " is a superclass of " + lowerBound.name + " (" + typeParamName + " >: " + + lowerBound.name + ")" +} + +trait UpperBoundedTypeParamConstraint extends TypeParamConstraint { + /** The lower bound */ + def upperBound: TypeEntity + + /** toString for debugging */ + override def toString = typeParamName + " is a subclass of " + upperBound.name + " (" + typeParamName + " <: " + + upperBound.name + ")" +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 670c9bbb3b..f295e4d211 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -17,7 +17,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 CommentFactory with TreeFactory => + thisFactory: ModelFactory with ModelFactoryImplicitSupport with CommentFactory with TreeFactory => import global._ import definitions.{ ObjectClass, RootPackage, EmptyPackage, NothingClass, AnyClass, AnyValClass, AnyRefClass } @@ -95,7 +95,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isDocTemplate = false } - abstract class MemberImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { + abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl = null, inTpl: => DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { lazy val comment = if (inTpl == null) None else thisFactory.comment(sym, inTpl) override def inTemplate = inTpl @@ -128,7 +128,14 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { if (sym.isImplicit) fgs += Paragraph(Text("implicit")) if (sym.isSealed) fgs += Paragraph(Text("sealed")) if (!sym.isTrait && (sym hasFlag Flags.ABSTRACT)) fgs += Paragraph(Text("abstract")) - if (!sym.isTrait && (sym hasFlag Flags.DEFERRED)) fgs += Paragraph(Text("abstract")) + /* Resetting the DEFERRED flag is a little trick here for refined types: (example from scala.collections) + * {{{ + * implicit def traversable2ops[T](t: collection.GenTraversableOnce[T]) = new TraversableOps[T] { + * def isParallel = ... + * }}} + * the type the method returns is TraversableOps, which has all-abstract symbols. But in reality, it couldn't have + * any abstract terms, otherwise it would fail compilation. So we reset the DEFERRED flag. */ + if (!sym.isTrait && (sym hasFlag Flags.DEFERRED) && (implConv eq null)) fgs += Paragraph(Text("abstract")) if (!sym.isModule && (sym hasFlag Flags.FINAL)) fgs += Paragraph(Text("final")) fgs.toList } @@ -162,7 +169,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case NullaryMethodType(res) => resultTpe(res) case _ => tpe } - makeTypeInTemplateContext(resultTpe(sym.tpe), inTemplate, sym) + val tpe = if (implConv eq null) sym.tpe else implConv.toType memberInfo sym + makeTypeInTemplateContext(resultTpe(tpe), inTemplate, sym) } def isDef = false def isVal = false @@ -173,15 +181,17 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isAliasType = false def isAbstractType = false def isAbstract = - ((!sym.isTrait && ((sym hasFlag Flags.ABSTRACT) || (sym hasFlag Flags.DEFERRED))) || + // for the explanation of implConv == null see comment on flags + ((!sym.isTrait && ((sym hasFlag Flags.ABSTRACT) || (sym hasFlag Flags.DEFERRED)) && (implConv == null)) || sym.isAbstractClass || sym.isAbstractType) && !sym.isSynthetic def isTemplate = false + def byConversion = if (implConv ne null) Some(implConv) else None } /** The instantiation of `TemplateImpl` triggers the creation of the following entities: * All ancestors of the template and all non-package members. */ - abstract class DocTemplateImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends MemberImpl(sym, inTpl) with TemplateImpl with HigherKindedImpl with DocTemplateEntity { + abstract class DocTemplateImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with DocTemplateEntity { //if (inTpl != null) println("mbr " + sym + " in " + (inTpl.toRoot map (_.sym)).mkString(" > ")) if (settings.verbose.value) inform("Creating doc template for " + sym) @@ -245,16 +255,20 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } def subClasses = if (subClassesCache == null) Nil else subClassesCache.toList - protected lazy val memberSyms = + val conversions = if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil + + lazy val memberSyms = // Only this class's constructors are part of its members, inherited constructors are not. sym.info.members.filter(s => localShouldDocument(s) && (!s.isConstructor || s.owner == sym) && !isPureBridge(sym) ) - val members = memberSyms flatMap (makeMember(_, this)) - val templates = members collect { case c: DocTemplateEntity => c } - val methods = members collect { case d: Def => d } - val values = members collect { case v: Val => v } - val abstractTypes = members collect { case t: AbstractType => t } - val aliasTypes = members collect { case t: AliasType => t } + val members = (memberSyms.flatMap(makeMember(_, null, this))) ::: + (conversions.flatMap((_.members))) // also take in the members from implicit conversions + + val templates = members collect { case c: DocTemplateEntity => c } + val methods = members collect { case d: Def => d } + val values = members collect { case v: Val => v } + val abstractTypes = members collect { case t: AbstractType => t } + val aliasTypes = members collect { case t: AliasType => t } override def isTemplate = true def isDocTemplate = true def companion = sym.companionSymbol match { @@ -273,18 +287,22 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity - abstract class NonTemplateMemberImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends MemberImpl(sym, inTpl) with NonTemplateMemberEntity { + abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity { override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name) - lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "#" + name) + lazy val definitionName = + if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + "#" + name) + else optimize(implConv.conversionQualifiedName + "#" + name) def isUseCase = sym.isSynthetic def isBridge = sym.isBridge } - abstract class NonTemplateParamMemberImpl(sym: Symbol, inTpl: => DocTemplateImpl) extends NonTemplateMemberImpl(sym, inTpl) { - def valueParams = - sym.paramss map { ps => (ps.zipWithIndex) map { case (p, i) => + abstract class NonTemplateParamMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl) extends NonTemplateMemberImpl(sym, implConv, inTpl) { + def valueParams = { + val info = if (implConv eq null) sym.info else implConv.toType memberInfo sym + info.paramss map { ps => (ps.zipWithIndex) map { case (p, i) => if (p.nameString contains "$") makeValueParam(p, inTpl, optimize("arg" + i)) else makeValueParam(p, inTpl) }} + } } abstract class ParameterImpl(sym: Symbol, inTpl: => TemplateImpl) extends EntityImpl(sym, inTpl) with ParameterEntity { @@ -356,7 +374,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override def qualifiedName = "_root_" override def inheritedFrom = Nil override def isRootPackage = true - override protected lazy val memberSyms = + override lazy val memberSyms = (bSym.info.members ++ EmptyPackage.info.members) filter { s => s != EmptyPackage && s != RootPackage } @@ -454,18 +472,19 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /** */ - def makeMember(aSym: Symbol, inTpl: => DocTemplateImpl): List[MemberImpl] = { + // TODO: Should be able to override the type + def makeMember(aSym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl): List[MemberImpl] = { def makeMember0(bSym: Symbol, _useCaseOf: Option[MemberImpl]): Option[MemberImpl] = { if (bSym.isGetter && bSym.isLazy) - Some(new NonTemplateMemberImpl(bSym, inTpl) with Val { + Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { override lazy val comment = // The analyser does not duplicate the lazy val's DocDef when it introduces its accessor. thisFactory.comment(bSym.accessed, inTpl) // This hack should be removed after analyser is fixed. override def isLazyVal = true override def useCaseOf = _useCaseOf }) else if (bSym.isGetter && bSym.accessed.isMutable) - Some(new NonTemplateMemberImpl(bSym, inTpl) with Val { + Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { override def isVar = true override def useCaseOf = _useCaseOf }) @@ -481,36 +500,36 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } else bSym } - Some(new NonTemplateParamMemberImpl(cSym, inTpl) with HigherKindedImpl with Def { + Some(new NonTemplateParamMemberImpl(cSym, implConv, inTpl) with HigherKindedImpl with Def { override def isDef = true override def useCaseOf = _useCaseOf }) } - else if (bSym.isConstructor) - Some(new NonTemplateParamMemberImpl(bSym, inTpl) with Constructor { + else if (bSym.isConstructor && (implConv == null)) + Some(new NonTemplateParamMemberImpl(bSym, implConv, inTpl) with Constructor { override def isConstructor = true def isPrimary = sym.isPrimaryConstructor override def useCaseOf = _useCaseOf }) else if (bSym.isGetter) // Scala field accessor or Java field - Some(new NonTemplateMemberImpl(bSym, inTpl) with Val { + Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { override def isVal = true override def useCaseOf = _useCaseOf }) else if (bSym.isAbstractType) - Some(new NonTemplateMemberImpl(bSym, inTpl) with TypeBoundsImpl with HigherKindedImpl with AbstractType { + Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with TypeBoundsImpl with HigherKindedImpl with AbstractType { override def isAbstractType = true override def useCaseOf = _useCaseOf }) - else if (bSym.isAliasType) - Some(new NonTemplateMemberImpl(bSym, inTpl) with HigherKindedImpl with AliasType { + else if (bSym.isAliasType && bSym != AnyRefClass) + Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with HigherKindedImpl with AliasType { override def isAliasType = true def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) override def useCaseOf = _useCaseOf }) else if (bSym.isPackage) inTpl match { case inPkg: PackageImpl => makePackage(bSym, inPkg) } - else if ((bSym.isClass || bSym.isModule) && templateShouldDocument(bSym)) + else if ((bSym.isClass || bSym.isModule || bSym == AnyRefClass) && templateShouldDocument(bSym)) Some(makeDocTemplate(bSym, inTpl)) else None @@ -520,16 +539,16 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { Nil else { val allSyms = useCases(aSym, inTpl.sym) map { case (bSym, bComment, bPos) => - docComments.put(bSym, DocComment(bComment, bPos)) // put the comment in the list, don't parse it yet, closes SI-4898 + docComments.put(bSym, DocComment(bComment, bPos)) // put the comment in the list, don't parse it yet, closes SI-4898 bSym } val member = makeMember0(aSym, None) - if (allSyms.isEmpty) - member.toList - else - // Use cases replace the original definitions - SI-5054 - allSyms flatMap { makeMember0(_, member) } + if (allSyms.isEmpty) + member.toList + else + // Use cases replace the original definitions - SI-5054 + allSyms flatMap { makeMember0(_, member) } } } @@ -639,9 +658,9 @@ 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) { nameBuffer append bSym.decodedName - else { + } else { val tpl = makeTemplate(bSym) val pos0 = nameBuffer.length refBuffer += pos0 -> (tpl, tpl.name.length) @@ -692,8 +711,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } def templateShouldDocument(aSym: Symbol): Boolean = { - // TODO: document sourceless entities (e.g., Any, etc), based on a new Setting to be added - (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && + // TODO: document sourceless entities (e.g., Any, etc), based on a new Setting to be added + (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && ( aSym.owner == NoSymbol || templateShouldDocument(aSym.owner) ) && !isEmptyJavaObject(aSym) } diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala new file mode 100644 index 0000000000..23bef02bed --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -0,0 +1,501 @@ +/* NSC -- new Scala compiler -- Copyright 2007-2012 LAMP/EPFL + * + * This trait finds implicit conversions for a class in the default scope and creates scaladoc entries for each of them. + * + * @author Vlad Ureche + * @author Adriaan Moors + */ + +package scala.tools.nsc +package doc +package model + +import comment._ + +import scala.collection._ +import scala.util.matching.Regex + +import symtab.Flags +import io._ + +import model.{ RootPackage => RootPackageEntity } + +/** + * This trait finds implicit conversions for a class in the default scope and creates scaladoc entries for each of them. + * + * Let's take this as an example: + * {{{ + * object Test { + * class A + * + * class B { + * def foo = 1 + * } + * + * class C extends B { + * def bar = 2 + * class implicit + * } + * + * D def conv(a: A) = new C + * } + * }}} + * + * Overview: + * - scaladoc-ing the above classes, `A` will get two more methods: foo and bar, over its default methods + * - the nested classes (specifically `D` above), abstract types, type aliases and constructor members are not added to + * `A` (see makeMember0 in ModelFactory, last 3 cases) + * - the members added by implicit conversion are always listed under the implicit conversion, not under the class they + * actually come from (`foo` will be listed as coming from the implicit conversion to `C` instead of `B`) - see + * `definitionName` in MemberImpl + * + * Internals: + * TODO: Give an overview here + */ +trait ModelFactoryImplicitSupport { + thisFactory: ModelFactory with CommentFactory with TreeFactory => + + import global._ + import global.analyzer._ + import global.definitions._ + import settings.hardcoded + + // 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) + + /** 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: + * {{{ + * class A[T] + * class B extends A[Int] + * class C extends A[String] + * implicit def pimpA[T: Numeric](a: A[T]): D + * }}} + * For B, no constraints are generated as Numeric[Int] is already in the default scope. On the other hand, for the + * conversion from C to D, depending on -implicits-show-all, the conversion can: + * - 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 = + if (convSym != NoSymbol) + makeTemplate(convSym.owner) + else { + error("Scaladoc implicits: Implicit conversion from " + sym.tpe + " to " + toType + " done by " + convSym + " = NoSymbol!") + makeRootPackage.get // surely the root package was created :) + } + + def 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 = convertorOwner.qualifiedName + "." + convSym.nameString + + lazy val constraints: List[Constraint] = constrs + + val members: List[MemberEntity] = { + // 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 && + isSameType(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 ============== */ + + /** + * Make the implicit conversion objects + * + * A word about the scope of the implicit conversions: currently we look at a very basic context composed of the + * default Scala imports (Predef._ for example) and the companion object of the current class, if one exists. In the + * future we might want to extend this to more complex scopes. + */ + def makeImplicitConversions(sym: Symbol, inTpl: => DocTemplateImpl): List[ImplicitConversion] = + // 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 + else { + var context: global.analyzer.Context = global.analyzer.rootContext(NoCompilationUnit) + + 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) + + // Filter out specialized conversions from array + if (sym == ArrayClass) + conversions = conversions.filterNot((conv: ImplicitConversion) => + hardcoded.arraySkipConversions.contains(conv.conversionQualifiedName)) + + // Filter out non-sensical conversions from value types + if (isScalaValueType(sym.tpe)) + conversions = conversions.filter((ic: ImplicitConversion) => + hardcoded.valueClassFilter(sym.nameString, ic.conversionQualifiedName)) + + // Put the class-specific conversions in front + val (ownConversions, commonConversions) = + conversions.partition(conv => !hardcoded.commonConversionTargets.contains(conv.conversionQualifiedName)) + + ownConversions ::: commonConversions + } + + /** makeImplicitConversion performs the heavier lifting to get the implicit listing: + * - for each possible conversion function (also called view) + * * figures out the final result of the view (to what is our class transformed?) + * * figures out the necessary constraints on the type parameters (such as T <: Int) and the context (such as Numeric[T]) + * * lists all inherited members + * + * What? in details: + * - say we start from a class A[T1, T2, T3, T4] + * - we have an implicit function (view) in scope: + * def pimpA[T3 <: Long, T4](a: A[Int, Foo[Bar[X]], T3, T4])(implicit ev1: Manifest[T4], ev2: Numeric[T4]): PimpedA + * - A is converted to PimpedA ONLY if a couple of constraints are satisfied: + * * T1 must be equal to Int + * * T2 must be equal to Foo[Bar[X]] + * * T3 must be upper bounded by Long + * * there must be evidence of Numeric[T4] and a Mainfest[T4] within scope + * - the final type is PimpedA and A therefore inherits a couple of members from pimpedA + * + * How? + * some notes: + * - Scala's type inference will want to solve all type parameters down to actual types, but we only want constraints + * to maintain generality + * - therefore, allViewsFrom wraps type parameters into "untouchable" type variables that only gather constraints, + * but are never solved down to a type + * - these must be reverted back to the type parameters and the constraints must be extracted and simplified (this is + * done by the uniteConstraints and boundedTParamsConstraints. Be sure to check them out + * - we also need to transform implicit parameters in the view's signature into constraints, such that Numeric[T4] + * appears as a constraint + */ + def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: => DocTemplateImpl): List[ImplicitConversion] = + if (result.tree == EmptyTree) Nil + else { + // `result` will contain the type of the view (= implicit conversion method) + // the search introduces untouchable type variables, but we want to get back to type parameters + val viewFullType = result.tree.tpe + // set the previously implicit parameters to being explicit + + val (viewSimplifiedType, viewImplicitTypes) = removeImplicitParameters(viewFullType) + + // TODO: Isolate this corner case :) - Predef.<%< and put it in the testsuite + if (viewSimplifiedType.params.length != 1) { + // This is known to be caused by the `<%<` object in Predef: + // {{{ + // sealed abstract class <%<[-From, +To] extends (From => To) with Serializable + // object <%< { + // implicit def conformsOrViewsAs[A <% B, B]: A <%< B = new (A <%< B) {def apply(x: A) = x} + // } + // }}} + // so we just won't generate an implicit conversion for implicit methods that only take implicit parameters + return Nil + } + + // type the view application so we get the exact type of the result (not the formal type) + val viewTree = result.tree.setType(viewSimplifiedType) + val appliedTree = new ApplyImplicitView(viewTree, List(Ident("") setType viewTree.tpe.paramTypes.head)) + val appliedTreeTyped: Tree = { + val newContext = context.makeImplicit(context.ambiguousErrors) + val newTyper = global.analyzer.newTyper(newContext) + newTyper.silent(_.typed(appliedTree, global.analyzer.EXPRmode, WildcardType), false) match { + + case global.analyzer.SilentResultValue(t: Tree) => t + case global.analyzer.SilentTypeError(err) => + global.reporter.warning(sym.pos, err.toString) + return Nil + } + } + + // now we have the final type: + val toType = wildcardToNothing(typeVarToOriginOrWildcard(appliedTreeTyped.tpe.finalResultType)) + + try { + // Transform bound constraints into scaladoc constraints + val implParamConstraints = makeImplicitConstraints(viewImplicitTypes, sym, context, inTpl) + val boundsConstraints = makeBoundedConstraints(sym.typeParams, constrs, inTpl) + // TODO: no substitution constraints appear in the library and compiler scaladoc. Maybe they can be removed? + val substConstraints = makeSubstitutionConstraints(result.subst, inTpl) + val constraints = implParamConstraints ::: boundsConstraints ::: substConstraints + + List(new ImplicitConversionImpl(sym, result.tree.symbol, toType, constraints, inTpl)) + } catch { + case i: ImplicitNotFound => + //println(" Eliminating: " + toType) + Nil + } + } + + 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) + val qualifiedName = implType.typeSymbol.ownerChain.reverse.map(_.nameString).mkString(".") + + var available: Option[Boolean] = None + + // see: https://groups.google.com/forum/?hl=en&fromgroups#!topic/scala-internals/gm_fr0RKzC4 + // + // println(implType + " => " + implType.isTrivial) + // var tpes: List[Type] = List(implType) + // while (!tpes.isEmpty) { + // val tpe = tpes.head + // tpes = tpes.tail + // tpe match { + // case TypeRef(pre, sym, args) => + // tpes = pre :: args ::: tpes + // println(tpe + " => " + tpe.isTrivial) + // case _ => + // println(tpe + " (of type" + tpe.getClass + ") => " + tpe.isTrivial) + // } + // } + // println("\n") + + // look for type variables in the type. If there are none, we can decide if the implicit is there or not + if (implType.isTrivial) { + try { + context.flushBuffer() /* any errors here should not prevent future findings */ + // TODO: Not sure this is the right thing to do -- seems similar to what scalac should be doing + val context2 = context.make(context.unit, context.tree, sym.owner, context.scope, context.imports) + val search = inferImplicit(EmptyTree, tpe, false, false, context2, false) + context.flushBuffer() /* any errors here should not prevent future findings */ + + available = Some(search.tree != EmptyTree) + } catch { + case _ => + } + } + + available match { + case Some(true) => + Nil + case Some(false) if (!implicitsShowAll) => + // if -implicits-show-all is not set, we get rid of impossible conversions (such as Numeric[String]) + throw new ImplicitNotFound(implType) + case _ => + val typeParamNames = sym.typeParams.map(_.name) + + // TODO: This is maybe the worst hack I ever did - it's as dirty as hell, but it seems to work, so until I + // learn more about symbols, it'll have to do. + implType match { + case TypeRef(pre, sym, List(TypeRef(NoPrefix, targ, Nil))) if (typeParamNames contains targ.name) => + hardcoded.knownTypeClasses.get(qualifiedName) match { + case Some(explanation) => + List(new KnownTypeClassConstraint { + val typeParamName = targ.nameString + val typeExplanation = explanation + val typeClassEntity = makeTemplate(sym) + val implicitType: TypeEntity = makeType(implType, inTpl) + }) + case None => + List(new TypeClassConstraint { + val typeParamName = targ.nameString + val typeClassEntity = makeTemplate(sym) + val implicitType: TypeEntity = makeType(implType, inTpl) + }) + } + case _ => + List(new ImplicitInScopeConstraint{ + val implicitType: TypeEntity = makeType(implType, inTpl) + }) + } + } + }) + + def makeSubstitutionConstraints(subst: TreeTypeSubstituter, inTpl: => DocTemplateImpl): List[Constraint] = + (subst.from zip subst.to) map { + case (from, to) => + new EqualTypeParamConstraint { + error("Scaladoc implicits: Unexpected type substitution constraint from: " + from + " to: " + to) + val typeParamName = from.toString + val rhs = makeType(to, inTpl) + } + } + + def makeBoundedConstraints(tparams: List[Symbol], constrs: List[TypeConstraint], inTpl: => DocTemplateImpl): List[Constraint] = + (tparams zip constrs) flatMap { + case (tparam, constr) => { + uniteConstraints(constr) match { + case (loBounds, upBounds) => (loBounds filter (_ != NothingClass.tpe), upBounds filter (_ != AnyClass.tpe)) match { + case (Nil, Nil) => + Nil + case (List(lo), List(up)) if (lo == up) => + List(new EqualTypeParamConstraint { + val typeParamName = tparam.nameString + val rhs = makeType(lo, inTpl) + }) + case (List(lo), List(up)) => + List(new BoundedTypeParamConstraint { + val typeParamName = tparam.nameString + val lowerBound = makeType(lo, inTpl) + val upperBound = makeType(up, inTpl) + }) + case (List(lo), Nil) => + List(new LowerBoundedTypeParamConstraint { + val typeParamName = tparam.nameString + val lowerBound = makeType(lo, inTpl) + }) + case (Nil, List(up)) => + List(new UpperBoundedTypeParamConstraint { + val typeParamName = tparam.nameString + val upperBound = makeType(up, inTpl) + }) + case other => + // this is likely an error on the lub/glb side + error("Scaladoc implicits: Error computing lub/glb for: " + (tparam, constr) + ":\n" + other) + Nil + } + } + } + } + + /** + * uniteConstraints takes a TypeConstraint instance and simplifies the constraints inside + * + * Normally TypeConstraint contains multiple lower and upper bounds, and we want to reduce this to a lower and an + * upper bound. Here are a couple of catches we need to be aware of: + * - before finding a view (implicit method in scope that maps class A[T1,T2,.. Tn] to something else) the type + * parameters are transformed into "untouchable" type variables so that type inference does not attempt to + * fully solve them down to a type but rather constrains them on both sides just enough for the view to be + * applicable -- now, we want to transform those type variables back to the original type parameters + * - some of the bounds fail type inference and therefore refer to Nothing => when performing unification (lub, glb) + * they start looking ugly => we (unsoundly) transform Nothing to WildcardType so we fool the unification algorithms + * into thinking there's nothing there + * - we don't want the wildcard types surviving the unification so we replace them back to Nothings + */ + def uniteConstraints(constr: TypeConstraint): (List[Type], List[Type]) = + try { + (List(wildcardToNothing(lub(constr.loBounds map typeVarToOriginOrWildcard))), + List(wildcardToNothing(glb(constr.hiBounds map typeVarToOriginOrWildcard)))) + } catch { + // does this actually ever happen? (probably when type vars occur in the bounds) + case x: Throwable => (constr.loBounds.distinct, constr.hiBounds.distinct) + } + + /** + * Make implicits explicit - Not used curently + */ + object implicitToExplicit extends TypeMap { + def apply(tp: Type): Type = mapOver(tp) match { + case MethodType(params, resultType) => + MethodType(params.map(param => if (param.isImplicit) param.cloneSymbol.resetFlag(Flags.IMPLICIT) else param), resultType) + case other => + other + } + } + + /** + * removeImplicitParameters transforms implicit parameters from the view result type into constraints and + * returns the simplified type of the view + * + * for the example view: + * implicit def pimpMyClass[T](a: MyClass[T])(implicit ev: Numeric[T]): PimpedMyClass[T] + * the implicit view result type is: + * (a: MyClass[T])(implicit ev: Numeric[T]): PimpedMyClass[T] + * and the simplified type will be: + * MyClass[T] => PimpedMyClass[T] + */ + def removeImplicitParameters(viewType: Type): (Type, List[Type]) = { + + val params = viewType.paramss.flatten + val (normalParams, implParams) = params.partition(!_.isImplicit) + val simplifiedType = MethodType(normalParams, viewType.finalResultType) + val implicitTypes = implParams.map(_.tpe) + + (simplifiedType, implicitTypes) + } + + /** + * typeVarsToOriginOrWildcard transforms the "untouchable" type variables into either their origins (the original + * type parameters) or into wildcard types if nothing matches + */ + object typeVarToOriginOrWildcard extends TypeMap { + def apply(tp: Type): Type = mapOver(tp) match { + case tv: TypeVar => + if (tv.constr.inst.typeSymbol == NothingClass) + WildcardType + else + tv.origin //appliedType(tv.origin.typeConstructor, tv.typeArgs map this) + case other => + if (other.typeSymbol == NothingClass) + WildcardType + else + other + } + } + + /** + * wildcardToNothing transforms wildcard types back to Nothing + */ + object wildcardToNothing extends TypeMap { + def apply(tp: Type): Type = mapOver(tp) match { + case WildcardType => + NothingClass.tpe + case other => + other + } + } + + /** implicitShouldDocument decides whether a member inherited by implicit conversion should be documented */ + def implicitShouldDocument(aSym: Symbol): Boolean = { + // We shouldn't document: + // - constructors + // - 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) && + (!aSym.isProtected) && (!aSym.isPrivate) && (!aSym.name.startsWith("_")) && + (aSym.isMethod || aSym.isGetter || aSym.isSetter) && + (aSym.nameString != "getClass") + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala b/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala index 988f2e0ba9..f948d53c8b 100755 --- a/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/TreeFactory.scala @@ -52,7 +52,7 @@ trait TreeFactory { thisTreeFactory: ModelFactory with TreeFactory => if (asym.isSetter) asym = asym.getter(asym.owner) makeTemplate(asym.owner) match { case docTmpl: DocTemplateImpl => - val mbrs: List[MemberImpl] = makeMember(asym,docTmpl) + val mbrs: List[MemberImpl] = makeMember(asym, null, docTmpl) mbrs foreach { mbr => refs += ((start, (mbr,end))) } case _ => } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 3a789b83b6..2de86c67bf 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -94,6 +94,27 @@ trait Implicits { result } + /** Find all views from type `tp` (in which `tpars` are free) + * + * Note that the trees in the search results in the returned list share the same type variables. + * Ignore their constr field! The list of type constraints returned along with each tree specifies the constraints that + * must be met by the corresponding type parameter in `tpars` (for the returned implicit view to be valid). + * + * @arg tp from-type for the implicit conversion + * @arg context search implicits here + * @arg tpars symbols that should be considered free type variables + * (implicit search should not try to solve them, just track their constraints) + */ + def allViewsFrom(tp: Type, context: Context, tpars: List[Symbol]): List[(SearchResult, List[TypeConstraint])] = { + // my untouchable typevars are better than yours (they can't be constrained by them) + val tvars = tpars map (TypeVar.apply(_, untouchable = true)) + val tpSubsted = tp.subst(tpars, tvars) + + val search = new ImplicitSearch(EmptyTree, functionType(List(tpSubsted), AnyClass.tpe), true, context.makeImplicit(false)) + + search.allImplicitsPoly(tvars) + } + private final val sizeLimit = 50000 private type Infos = List[ImplicitInfo] private type Infoss = List[List[ImplicitInfo]] @@ -369,7 +390,7 @@ trait Implicits { private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean): SearchResult = { (context.openImplicits find { case (tp, tree1) => tree1.symbol == tree.symbol && dominates(pt, tp)}) match { case Some(pending) => - // println("Pending implicit "+pending+" dominates "+pt+"/"+undetParams) //@MDEBUG + //println("Pending implicit "+pending+" dominates "+pt+"/"+undetParams) //@MDEBUG throw DivergentImplicit case None => try { @@ -378,7 +399,7 @@ trait Implicits { typedImplicit0(info, ptChecked) } catch { case ex: DivergentImplicit => - // println("DivergentImplicit for pt:"+ pt +", open implicits:"+context.openImplicits) //@MDEBUG + //println("DivergentImplicit for pt:"+ pt +", open implicits:"+context.openImplicits) //@MDEBUG if (context.openImplicits.tail.isEmpty) { if (!(pt.isErroneous)) DivergingImplicitExpansionError(tree, pt, info.sym)(context) @@ -510,7 +531,7 @@ trait Implicits { private def typedImplicit0(info: ImplicitInfo, ptChecked: Boolean): SearchResult = { incCounter(plausiblyCompatibleImplicits) - printTyping( + printTyping ( ptBlock("typedImplicit0", "info.name" -> info.name, "ptChecked" -> ptChecked, @@ -1202,6 +1223,26 @@ trait Implicits { def search(iss: Infoss, isLocal: Boolean) = applicableInfos(iss, isLocal).values (search(context.implicitss, true) ++ search(implicitsOfExpectedType, false)).toList.filter(_.tree ne EmptyTree) } + + // find all implicits for some type that contains type variables + // collect the constraints that result from typing each implicit + def allImplicitsPoly(tvars: List[TypeVar]): List[(SearchResult, List[TypeConstraint])] = { + def resetTVars() = tvars foreach { _.constr = new TypeConstraint } + + def eligibleInfos(iss: Infoss, isLocal: Boolean) = new ImplicitComputation(iss, if (isLocal) util.HashSet[Name](512) else null).eligible + val allEligibleInfos = (eligibleInfos(context.implicitss, true) ++ eligibleInfos(implicitsOfExpectedType, false)).toList + + allEligibleInfos flatMap { ii => + // each ImplicitInfo contributes a distinct set of constraints (generated indirectly by typedImplicit) + // thus, start each type var off with a fresh for every typedImplicit + resetTVars() + // any previous errors should not affect us now + context.flushBuffer() + val res = typedImplicit(ii, false) + if (res.tree ne EmptyTree) List((res, tvars map (_.constr))) + else Nil + } + } } object ImplicitNotFoundMsg { diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 98b8d7673e..d2fe106b14 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -83,7 +83,7 @@ trait Infer { def apply(tp: Type): Type = tp match { case WildcardType | BoundedWildcardType(_) | NoType => throw new NoInstance("undetermined type") - case tv @ TypeVar(origin, constr) => + case tv @ TypeVar(origin, constr) if !tv.untouchable => if (constr.inst == NoType) { throw new DeferredNoInstance(() => "no unique instantiation of type variable " + origin + " could be found") diff --git a/src/library/scala/Array.scala b/src/library/scala/Array.scala index 99c54ce58c..5b8ebde308 100644 --- a/src/library/scala/Array.scala +++ b/src/library/scala/Array.scala @@ -467,6 +467,19 @@ object Array extends FallbackArrayBuilding { * @version 1.0 * @see [[http://www.scala-lang.org/docu/files/collections-api/collections_38.html#anchor "The Scala 2.8 Collections' API"]] * section on `Array` by Martin Odersky for more information. + * @define coll array + * @define Coll Array + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define collectExample + * @define undefinedorder + * @define thatinfo the class of the returned collection. In the standard library configuration, + * `That` is either `Array[B]` if a ClassManifest is available for B or `ArraySeq[B]` otherwise. + * @define zipthatinfo $thatinfo + * @define bfinfo an implicit value of class `CanBuildFrom` which determines the result class `That` from the current + * representation type `Repr` and the new element type `B`. */ final class Array[T](_length: Int) extends java.io.Serializable with java.lang.Cloneable { diff --git a/src/library/scala/Option.scala b/src/library/scala/Option.scala index 2d87ccb261..68ea67ca00 100644 --- a/src/library/scala/Option.scala +++ b/src/library/scala/Option.scala @@ -79,6 +79,17 @@ object Option { * @define option [[scala.Option]] * @define p `p` * @define f `f` + * @define coll option + * @define Coll Option + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define collectExample + * @define undefinedorder + * @define thatinfo the class of the returned collection. In the standard library configuration, `That` is `Iterable[B]` + * @define bfinfo an implicit value of class `CanBuildFrom` which determines the result class `That` from the current + * representation type `Repr` and the new element type `B`. */ sealed abstract class Option[+A] extends Product with Serializable { self => diff --git a/src/library/scala/Tuple2.scala b/src/library/scala/Tuple2.scala index b1befca4fa..37ab564c3c 100644 --- a/src/library/scala/Tuple2.scala +++ b/src/library/scala/Tuple2.scala @@ -23,7 +23,7 @@ case class Tuple2[@specialized(Int, Long, Double, Char, Boolean, AnyRef) +T1, @s extends Product2[T1, T2] { override def toString() = "(" + _1 + "," + _2 + ")" - + /** Swaps the elements of this `Tuple`. * @return a new Tuple where the first element is the second element of this Tuple and the * second element is the first element of this Tuple. @@ -54,6 +54,16 @@ case class Tuple2[@specialized(Int, Long, Double, Char, Boolean, AnyRef) +T1, @s def zipped[Repr1, El1, Repr2, El2](implicit w1: T1 => TLike[El1, Repr1], w2: T2 => ILike[El2, Repr2]): Zipped[Repr1, El1, Repr2, El2] = new Zipped[Repr1, El1, Repr2, El2](_1, _2) + /** + * @define coll zipped + * @define Coll Zipped + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define collectExample + * @define undefinedorder + */ class Zipped[+Repr1, +El1, +Repr2, +El2](coll1: TLike[El1, Repr1], coll2: ILike[El2, Repr2]) { // coll2: ILike for filter def map[B, To](f: (El1, El2) => B)(implicit cbf: CBF[Repr1, B, To]): To = { val b = cbf(coll1.repr) diff --git a/src/library/scala/Tuple3.scala b/src/library/scala/Tuple3.scala index 0d5399308b..cd5ee23757 100644 --- a/src/library/scala/Tuple3.scala +++ b/src/library/scala/Tuple3.scala @@ -24,7 +24,7 @@ case class Tuple3[+T1, +T2, +T3](_1: T1, _2: T2, _3: T3) extends Product3[T1, T2, T3] { override def toString() = "(" + _1 + "," + _2 + "," + _3 + ")" - + @deprecated("Use `zipped` instead.", "2.9.0") def zip[Repr1, El1, El2, El3, To](implicit w1: T1 => TLike[El1, Repr1], @@ -53,6 +53,17 @@ case class Tuple3[+T1, +T2, +T3](_1: T1, _2: T2, _3: T3) w3: T3 => ILike[El3, Repr3]): Zipped[Repr1, El1, Repr2, El2, Repr3, El3] = new Zipped[Repr1, El1, Repr2, El2, Repr3, El3](_1, _2, _3) + /** + * @define coll zipped + * @define Coll Zipped + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define collectExample + * @define undefinedorder + * @define thatInfo The class of the returned collection. + */ class Zipped[+Repr1, +El1, +Repr2, +El2, +Repr3, +El3](coll1: TLike[El1, Repr1], coll2: ILike[El2, Repr2], coll3: ILike[El3, Repr3]) { diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index 2eb026ceee..142f2baea5 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -21,10 +21,10 @@ import scala.tools.nsc.reporters.ConsoleReporter import scala.tools.nsc.doc.model._ import scala.tools.partest.ScaladocModelTest - object Test extends ScaladocModelTest { + object Test extends ScaladocModelTest { - def code = """ ... """ - def scaladocSettings = "" + override def code = """ ... """ // or override def resourceFile = ".scala" (from test/scaladoc/resources) + def scaladocSettings = " ... " 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._ @@ -39,10 +39,22 @@ abstract class ScaladocModelTest extends DirectTest { /** Override this to give scaladoc command line parameters */ def scaladocSettings: String - + /** Override this to test the model */ def testModel(root: Package): Unit + /** Override to feed a file in resources to scaladoc*/ + def resourceFile: String = null + + /** Override to feed code into scaladoc */ + override def code = + if (resourceFile ne null) + io.File(resourcePath + "/" + resourceFile).slurp() + else + sys.error("Scaladoc Model Test: You need to give a file or some code to feed to scaladoc!") + + def resourcePath = io.Directory(sys.props("partest.cwd") + "/../resources") + // Implementation follows: override def extraSettings: String = "-usejavacp" @@ -50,15 +62,15 @@ abstract class ScaladocModelTest extends DirectTest { // redirect err to out, for logging val prevErr = System.err System.setErr(System.out) - + try { // 1 - compile with scaladoc and get the model out - val args = scaladocSettings.split(" ") - val universe = model(args:_*).getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")}) + val universe = model.getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")}) // 2 - check the model generated testModel(universe.rootPackage) + println("Done.") } catch { - case e => + case e => println(e) e.printStackTrace } @@ -66,51 +78,46 @@ abstract class ScaladocModelTest extends DirectTest { System.setErr(prevErr) } + private[this] var settings: Settings = null + // create a new scaladoc compiler - def newDocFactory(args: String*): DocFactory = { - val settings = new Settings(_ => ()) - val command = new ScalaDoc.Command((CommandLineParser tokenize extraSettings) ++ args.toList, settings) + def newDocFactory: DocFactory = { + settings = new Settings(_ => ()) + settings.reportModel = false // 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) docFact } // compile with scaladoc and output the result - def model(args: String*): Option[Universe] = newDocFactory(args: _*).makeUniverse(Right(code)) + def model: Option[Universe] = newDocFactory.makeUniverse(Right(code)) // so we don't get the newSettings warning - override def isDebug = false + override def isDebug = false // finally, enable easy navigation inside the entities object access { - // Make it easy to access things class TemplateAccess(tpl: DocTemplateEntity) { - def _class(name: String): DocTemplateEntity = getTheFirst(_classes(name), tpl.qualifiedName + ".class(" + name + ")") - def _classes(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).flatMap({ case c: Class => List(c)}) + def _classes(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: Class => c}) def _trait(name: String): DocTemplateEntity = getTheFirst(_traits(name), tpl.qualifiedName + ".trait(" + name + ")") - def _traits(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).flatMap({ case t: Trait => List(t)}) + def _traits(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: Trait => t}) def _object(name: String): DocTemplateEntity = getTheFirst(_objects(name), tpl.qualifiedName + ".object(" + name + ")") - def _objects(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).flatMap({ case o: Object => List(o)}) + def _objects(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: Object => o}) def _method(name: String): Def = getTheFirst(_methods(name), tpl.qualifiedName + ".method(" + name + ")") def _methods(name: String): List[Def] = tpl.methods.filter(_.name == name) - + def _value(name: String): Val = getTheFirst(_values(name), tpl.qualifiedName + ".value(" + name + ")") def _values(name: String): List[Val] = tpl.values.filter(_.name == name) - def getTheFirst[T](list: List[T], expl: String): T = { - if (list.length == 1) - list.head - else if (list.length == 0) - sys.error("Error getting " + expl + ": No such element. All elements in list: [" + list.mkString(", ") + "]") - else - sys.error("Error getting " + expl + ": " + list.length + " elements with this name. " + - "All elements in list: [" + list.mkString(", ") + "]") - } + def _conversion(name: String): ImplicitConversion = getTheFirst(_conversions(name), tpl.qualifiedName + ".conversion(" + name + ")") + def _conversions(name: String): List[ImplicitConversion] = tpl.conversions.filter(_.conversionQualifiedName == name) } class PackageAccess(pack: Package) extends TemplateAccess(pack) { @@ -118,7 +125,22 @@ abstract class ScaladocModelTest extends DirectTest { def _packages(name: String): List[Package] = pack.packages.filter(_.name == name) } + class MemberAccess(mbrs: WithMembers) { + def _member(name: String): MemberEntity = getTheFirst(_members(name), mbrs.toString + ".member(" + name + ")") + def _members(name: String): List[MemberEntity] = mbrs.members.filter(_.name == name) + } + + type WithMembers = { def members: List[MemberEntity]; def toString: String } /* DocTemplates and ImplicitConversions */ + implicit def templateAccess(tpl: DocTemplateEntity) = new TemplateAccess(tpl) implicit def packageAccess(pack: Package) = new PackageAccess(pack) + implicit def membersAccess(mbrs: WithMembers) = new MemberAccess(mbrs) + + def getTheFirst[T](list: List[T], expl: String): T = list.length match { + case 1 => list.head + case 0 => sys.error("Error getting " + expl + ": No such element.") + case _ => sys.error("Error getting " + expl + ": " + list.length + " elements with this name. " + + "All elements in list: [" + list.mkString(", ") + "]") + } } } -- cgit v1.2.3