/* NSC -- new Scala compiler -- Copyright 2007-2011 LAMP/EPFL */
package scala.tools.nsc
package doc
package model
import comment._
import diagram._
import scala.collection._
import scala.util.matching.Regex
import symtab.Flags
import io._
import model.{ RootPackage => RootPackageEntity }
/** This trait extracts all required information for documentation from compilation units */
class ModelFactory(val global: Global, val settings: doc.Settings) {
thisFactory: ModelFactory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with TreeFactory =>
import global._
import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass }
import rootMirror.{ RootPackage, RootClass, EmptyPackage }
def templatesCount = docTemplatesCache.count(_._2.isDocTemplate) - droppedPackages.size
private var _modelFinished = false
def modelFinished: Boolean = _modelFinished
private var universe: Universe = null
private def dbg(msg: String) = if (sys.props contains "scala.scaladoc.debug") println(msg)
private def closestPackage(sym: Symbol) = {
if (sym.isPackage || sym.isPackageClass) sym
else sym.enclosingPackage
}
private def printWithoutPrefix(memberSym: Symbol, templateSym: Symbol) = {
dbg(
"memberSym " + memberSym + " templateSym " + templateSym + " encls = " +
closestPackage(memberSym) + ", " + closestPackage(templateSym)
)
memberSym.isOmittablePrefix || (closestPackage(memberSym) == closestPackage(templateSym))
}
def makeModel: Option[Universe] = {
val universe = new Universe { thisUniverse =>
thisFactory.universe = thisUniverse
val settings = thisFactory.settings
val rootPackage = modelCreation.createRootPackage
}
_modelFinished = true
// complete the links between model entities, everthing that couldn't have been done before
universe.rootPackage.completeModel
Some(universe) filter (_.rootPackage != null)
}
// state:
var ids = 0
private val droppedPackages = mutable.Set[PackageImpl]()
protected val docTemplatesCache = new mutable.LinkedHashMap[Symbol, DocTemplateImpl]
protected val noDocTemplatesCache = new mutable.LinkedHashMap[Symbol, NoDocTemplateImpl]
protected var typeCache = new mutable.LinkedHashMap[Type, TypeEntity]
def optimize(str: String): String =
if (str.length < 16) str.intern else str
/* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */
abstract class EntityImpl(val sym: Symbol, val inTpl: TemplateImpl) extends Entity {
val id = { ids += 1; ids }
val name = optimize(sym.nameString)
val universe = thisFactory.universe
// Debugging:
// assert(id != 36, sym + " " + sym.getClass)
//println("Creating entity #" + id + " [" + kind + " " + qualifiedName + "] for sym " + sym.kindString + " " + sym.ownerChain.reverse.map(_.name).mkString("."))
def inTemplate: TemplateImpl = inTpl
def toRoot: List[EntityImpl] = this :: inTpl.toRoot
def qualifiedName = name
def annotations = sym.annotations.map(makeAnnotation)
def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject
}
trait TemplateImpl extends EntityImpl with TemplateEntity {
override def qualifiedName: String =
if (inTemplate == null || inTemplate.isRootPackage) name else optimize(inTemplate.qualifiedName + "." + name)
def isPackage = sym.isPackage
def isTrait = sym.isTrait
def isClass = sym.isClass && !sym.isTrait
def isObject = sym.isModule && !sym.isPackage
def isCaseClass = sym.isCaseClass
def isRootPackage = false
def ownType = makeType(sym.tpe, this)
def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this))
}
abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity {
lazy val comment = {
val commentTpl =
/* Variable precendence order for implicitly added members: Take the variable defifinitions from ...
* 1. the target of the implicit conversion
* 2. the definition template (owner)
* 3. the current template
*/
if (implConv != null) findTemplateMaybe(implConv.toType.typeSymbol) match {
case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package
case _ => findTemplateMaybe(sym.owner) match {
case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package
case _ => inTpl
}
} else inTpl
if (commentTpl != null) thisFactory.comment(sym, commentTpl) else None
}
override def inTemplate = inTpl
override def toRoot: List[MemberImpl] = this :: inTpl.toRoot
def inDefinitionTemplates = this match {
case mb: NonTemplateMemberEntity if (mb.useCaseOf.isDefined) =>
mb.useCaseOf.get.inDefinitionTemplates
case _ =>
if (inTpl == null)
List(makeRootPackage)
else
makeTemplate(sym.owner)::(sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) })
}
def visibility = {
if (sym.isPrivateLocal) PrivateInInstance()
else if (sym.isProtectedLocal) ProtectedInInstance()
else {
val qual =
if (sym.hasAccessBoundary)
Some(makeTemplate(sym.privateWithin))
else None
if (sym.isPrivate) PrivateInTemplate(inTpl)
else if (sym.isProtected) ProtectedInTemplate(qual getOrElse inTpl)
else if (qual.isDefined) PrivateInTemplate(qual.get)
else Public()
}
}
def flags = {
val fgs = mutable.ListBuffer.empty[Paragraph]
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"))
/* 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
}
def deprecation =
if (sym.isDeprecated)
Some((sym.deprecationMessage, sym.deprecationVersion) match {
case (Some(msg), Some(ver)) => parseWiki("''(Since version " + ver + ")'' " + msg, NoPosition)
case (Some(msg), None) => parseWiki(msg, NoPosition)
case (None, Some(ver)) => parseWiki("''(Since version " + ver + ")''", NoPosition)
case (None, None) => Body(Nil)
})
else
comment flatMap { _.deprecated }
def migration =
if(sym.hasMigrationAnnotation)
Some((sym.migrationMessage, sym.migrationVersion) match {
case (Some(msg), Some(ver)) => parseWiki("''(Changed in version " + ver + ")'' " + msg, NoPosition)
case (Some(msg), None) => parseWiki(msg, NoPosition)
case (None, Some(ver)) => parseWiki("''(Changed in version " + ver + ")''", NoPosition)
case (None, None) => Body(Nil)
})
else
None
def inheritedFrom =
if (inTemplate.sym == this.sym.owner || inTemplate.sym.isPackage) Nil else
makeTemplate(this.sym.owner) :: (sym.allOverriddenSymbols map { os => makeTemplate(os.owner) })
def resultType = {
def resultTpe(tpe: Type): Type = tpe match { // similar to finalResultType, except that it leaves singleton types alone
case PolyType(_, res) => resultTpe(res)
case MethodType(_, res) => resultTpe(res)
case NullaryMethodType(res) => resultTpe(res)
case _ => tpe
}
val tpe = if (implConv eq null) sym.tpe else implConv.toType memberInfo sym
makeTypeInTemplateContext(resultTpe(tpe), inTemplate, sym)
}
def isDef = false
def isVal = false
def isLazyVal = false
def isVar = false
def isImplicit = sym.isImplicit
def isConstructor = false
def isAliasType = false
def isAbstractType = false
def isAbstract =
// 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
lazy val signature = {
val defParamsString = this match {
case d: MemberEntity with Def =>
val paramLists: List[String] =
if (d.valueParams.isEmpty) Nil
else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")"))
val tParams = if (d.typeParams.isEmpty) "" else {
def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = {
def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match {
case None => ""
case Some(tpe) => pre ++ tpe.toString
}
bound0(hi, "<:") ++ bound0(lo, ">:")
}
"[" + d.typeParams.map(tp => tp.variance + tp.name + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]"
}
tParams + paramLists.mkString
case _ => ""
}
name + defParamsString +":"+ resultType.name
}
}
/** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class
* exists, but should not be documented (either it's not included in the source or it's not visible)
*/
class NoDocTemplateImpl(sym: Symbol, inTpl: TemplateImpl) extends EntityImpl(sym, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplate {
assert(modelFinished)
assert(!(noDocTemplatesCache isDefinedAt sym))
noDocTemplatesCache += (sym -> this)
def isDocTemplate = false
}
/** An inherited template that was not documented in its original owner - example:
* in classpath: trait T { class C } -- T (and implicitly C) are not documented
* in the source: trait U extends T -- C appears in U as a NoDocTemplateMemberImpl -- that is, U has a member for it
* but C doesn't get its own page
*/
class NoDocTemplateMemberImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplateMemberEntity {
assert(modelFinished)
// no templates cache for this class, each owner gets its own instance
override def isTemplate = true
def isDocTemplate = false
lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name)
}
/** The instantiation of `TemplateImpl` triggers the creation of the following entities:
* All ancestors of the template and all non-package members.
*/
abstract class DocTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with DocTemplateEntity {
assert(!modelFinished)
assert(!(docTemplatesCache isDefinedAt sym), sym)
docTemplatesCache += (sym -> this)
if (settings.verbose.value)
inform("Creating doc template for " + sym)
override def toRoot: List[DocTemplateImpl] = this :: inTpl.toRoot
def inSource =
if (sym.sourceFile != null && ! sym.isSynthetic)
Some((sym.sourceFile, sym.pos.line))
else
None
def sourceUrl = {
def fixPath(s: String) = s.replaceAll("\\" + java.io.File.separator, "/")
val assumedSourceRoot = fixPath(settings.sourcepath.value) stripSuffix "/"
if (!settings.docsourceurl.isDefault)
inSource map { case (file, _) =>
val filePath = fixPath(file.path).replaceFirst("^" + assumedSourceRoot, "").stripSuffix(".scala")
val tplOwner = this.inTemplate.qualifiedName
val tplName = this.name
val patches = new Regex("""€\{(FILE_PATH|TPL_OWNER|TPL_NAME)\}""")
def substitute(name: String): String = name match {
case "FILE_PATH" => filePath
case "TPL_OWNER" => tplOwner
case "TPL_NAME" => tplName
}
val patchedString = patches.replaceAllIn(settings.docsourceurl.value, m => java.util.regex.Matcher.quoteReplacement(substitute(m.group(1))) )
new java.net.URL(patchedString)
}
else None
}
def parentTemplates =
if (sym.isPackage || sym == AnyClass)
List()
else
sym.tpe.parents.flatMap { tpe: Type =>
val tSym = tpe.typeSymbol
if (tSym != NoSymbol)
List(makeTemplate(tSym))
else
List()
} filter (_.isInstanceOf[DocTemplateEntity])
def parentTypes =
if (sym.isPackage || sym == AnyClass) List() else {
val tps = sym.tpe.parents map { _.asSeenFrom(sym.thisType, sym) }
makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl)
}
protected def linearizationFromSymbol(symbol: Symbol): List[(TemplateEntity, TypeEntity)] = {
symbol.ancestors map { ancestor =>
val typeEntity = makeType(symbol.info.baseType(ancestor), this)
val tmplEntity = makeTemplate(ancestor) match {
case tmpl: DocTemplateImpl => tmpl registerSubClass this ; tmpl
case tmpl => tmpl
}
(tmplEntity, typeEntity)
}
}
lazy val linearization = linearizationFromSymbol(sym)
def linearizationTemplates = linearization map { _._1 }
def linearizationTypes = linearization map { _._2 }
/* Subclass cache */
private lazy val subClassesCache = (
if (sym == AnyRefClass) null
else mutable.ListBuffer[DocTemplateEntity]()
)
def registerSubClass(sc: DocTemplateEntity): Unit = {
if (subClassesCache != null)
subClassesCache += sc
}
def allSubClasses = if (subClassesCache == null) Nil else subClassesCache.toList
def directSubClasses = allSubClasses.filter(_.parentTypes.map(_._1).contains(this))
/* Implcitly convertible class cache */
private var implicitlyConvertibleClassesCache: mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)] = null
def registerImplicitlyConvertibleClass(dtpl: DocTemplateEntity, conv: ImplicitConversionImpl): Unit = {
if (implicitlyConvertibleClassesCache == null)
implicitlyConvertibleClassesCache = mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)]()
implicitlyConvertibleClassesCache += ((dtpl, conv))
}
def incomingImplicitlyConvertedClasses: List[(DocTemplateEntity, ImplicitConversionImpl)] =
if (implicitlyConvertibleClassesCache == null)
List()
else
implicitlyConvertibleClassesCache.toList
// the implicit conversions are generated eagerly, but the members generated by implicit conversions are added
// lazily, on completeModel
val conversions: List[ImplicitConversionImpl] =
if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil
// members as given by the compiler
lazy val memberSyms = sym.info.members.filter(s => membersShouldDocument(s, this))
// the inherited templates (classes, traits or objects)
var memberSymsLazy = memberSyms.filter(t => templateShouldDocument(t, this) && !inOriginalOnwer(t, this))
// the direct members (methods, values, vars, types and directly contained templates)
var memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_))
// the members generated by the symbols in memberSymsEager
val ownMembers = (memberSyms.flatMap(makeMember(_, null, this)))
// all the members that are documentented PLUS the members inherited by implicit conversions
var members: List[MemberImpl] = ownMembers
def templates = members collect { case c: TemplateEntity with MemberEntity => c }
def methods = members collect { case d: Def => d }
def values = members collect { case v: Val => v }
def abstractTypes = members collect { case t: AbstractType => t }
def aliasTypes = members collect { case t: AliasType => t }
/**
* This is the final point in the core model creation: no DocTemplates are created after the model has finished, but
* inherited templates and implicit members are added to the members at this point.
*/
def completeModel: Unit = {
// DFS completion
for (member <- members)
member match {
case d: DocTemplateImpl => d.completeModel
case _ =>
}
members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, this))
// compute linearization to register subclasses
linearization
outgoingImplicitlyConvertedClasses
// the members generated by the symbols in memberSymsEager PLUS the members from the usecases
val allMembers = ownMembers ::: ownMembers.flatMap(_.useCaseOf.map(_.asInstanceOf[MemberImpl])).distinct
implicitsShadowing = makeShadowingTable(allMembers, conversions, this)
// finally, add the members generated by implicit conversions
members :::= conversions.flatMap(_.memberImpls)
}
var implicitsShadowing = Map[MemberEntity, ImplicitMemberShadowing]()
lazy val outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity, ImplicitConversionImpl)] =
conversions flatMap (conv =>
if (!implicitExcluded(conv.conversionQualifiedName))
conv.targetTypeComponents map {
case pair@(template, tpe) =>
template match {
case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this, conv)
case _ => // nothing
}
(pair._1, pair._2, conv)
}
else List()
)
override def isTemplate = true
lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name)
def isDocTemplate = true
def companion = sym.companionSymbol match {
case NoSymbol => None
case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) =>
makeTemplate(comSym) match {
case d: DocTemplateImpl => Some(d)
case _ => None
}
case _ => None
}
// We make the diagram a lazy val, since we're not sure we'll include the diagrams in the page
lazy val inheritanceDiagram = makeInheritanceDiagram(this)
lazy val contentDiagram = makeContentDiagram(this)
}
abstract class PackageImpl(sym: Symbol, inTpl: PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package {
override def inTemplate = inTpl
override def toRoot: List[PackageImpl] = this :: inTpl.toRoot
override lazy val linearization = {
val symbol = sym.info.members.find {
s => s.isPackageObject
} getOrElse sym
linearizationFromSymbol(symbol)
}
def packages = members collect { case p: PackageImpl if !(droppedPackages contains p) => p }
}
abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity
abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity {
override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name)
lazy val definitionName = {
// this contrived name is here just to satisfy some older tests -- if you decide to remove it, be my guest, and
// also remove property("package object") from test/scaladoc/scalacheck/HtmlFactoryTest.scala so you don't break
// the test suite...
val packageObject = if (inPackageObject) ".package" else ""
if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + packageObject + "#" + name)
else optimize(implConv.conversionQualifiedName + packageObject + "#" + name)
}
def isUseCase = sym.isSynthetic
def isBridge = sym.isBridge
}
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(val sym: Symbol, val inTpl: TemplateImpl) extends ParameterEntity {
val name = optimize(sym.nameString)
}
private trait TypeBoundsImpl {
def sym: Symbol
def inTpl: TemplateImpl
def lo = sym.info.bounds match {
case TypeBounds(lo, hi) if lo.typeSymbol != NothingClass =>
Some(makeTypeInTemplateContext(appliedType(lo, sym.info.typeParams map {_.tpe}), inTpl, sym))
case _ => None
}
def hi = sym.info.bounds match {
case TypeBounds(lo, hi) if hi.typeSymbol != AnyClass =>
Some(makeTypeInTemplateContext(appliedType(hi, sym.info.typeParams map {_.tpe}), inTpl, sym))
case _ => None
}
}
trait HigherKindedImpl extends HigherKinded {
def sym: Symbol
def inTpl: TemplateImpl
def typeParams =
sym.typeParams map (makeTypeParam(_, inTpl))
}
/* ============== MAKER METHODS ============== */
/** */
def normalizeTemplate(aSym: Symbol): Symbol = aSym match {
case null | rootMirror.EmptyPackage | NoSymbol =>
normalizeTemplate(RootPackage)
case ObjectClass =>
normalizeTemplate(AnyRefClass)
case _ if aSym.isPackageObject =>
normalizeTemplate(aSym.owner)
case _ if aSym.isModuleClass =>
normalizeTemplate(aSym.sourceModule)
case _ =>
aSym
}
/**
* These are all model construction methods. Please do not use them directly, they are calling each other recursively
* starting from makeModel. On the other hand, makeTemplate, makeAnnotation, makeMember, makeType should only be used
* after the model was created (modelFinished=true) otherwise assertions will start failing.
*/
object modelCreation {
def createRootPackage: PackageImpl = docTemplatesCache.get(RootPackage) match {
case Some(root: PackageImpl) => root
case _ => modelCreation.createTemplate(RootPackage, null).asInstanceOf[PackageImpl]
}
/**
* Create a template, either a package, class, trait or object
*/
def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = {
// don't call this after the model finished!
assert(!modelFinished)
def createRootPackageComment: Option[Comment] =
if(settings.docRootContent.isDefault) None
else {
import Streamable._
Path(settings.docRootContent.value) match {
case f : File => {
val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition))
Some(rootComment)
}
case _ => None
}
}
def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = {
assert(!modelFinished) // only created BEFORE the model is finished
if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule))
new DocTemplateImpl(bSym, inTpl) with Object
else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait))
new DocTemplateImpl(bSym, inTpl) with Trait
else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass))
new DocTemplateImpl(bSym, inTpl) with Class {
def valueParams =
// we don't want params on a class (non case class) signature
if (isCaseClass) primaryConstructor match {
case Some(const) => const.sym.paramss map (_ map (makeValueParam(_, this)))
case None => List()
}
else List.empty
val constructors =
members collect { case d: Constructor => d }
def primaryConstructor = constructors find { _.isPrimary }
}
else
sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a documentable template")
}
val bSym = normalizeTemplate(aSym)
if (docTemplatesCache isDefinedAt bSym)
return docTemplatesCache(bSym)
/* Three cases of templates:
* (1) root package -- special cased for bootstrapping
* (2) package
* (3) class/object/trait
*/
if (bSym == RootPackage) // (1)
new RootPackageImpl(bSym) {
override lazy val comment = createRootPackageComment
override val name = "root"
override def inTemplate = this
override def toRoot = this :: Nil
override def qualifiedName = "_root_"
override def inheritedFrom = Nil
override def isRootPackage = true
override lazy val memberSyms =
(bSym.info.members ++ EmptyPackage.info.members) filter { s =>
s != EmptyPackage && s != RootPackage
}
}
else if (bSym.isPackage) // (2)
inTpl match {
case inPkg: PackageImpl =>
val pack = new PackageImpl(bSym, inPkg) {}
if (pack.templates.isEmpty && pack.memberSymsLazy.isEmpty)
droppedPackages += pack
pack
case _ =>
sys.error("'" + bSym + "' must be in a package")
}
else {
// no class inheritance at this point
assert(inOriginalOnwer(bSym, inTpl))
createDocTemplate(bSym, inTpl)
}
}
/**
* After the model is completed, no more DocTemplateEntities are created.
* Therefore any symbol that still appears is:
* - NoDocTemplateMemberEntity (created here)
* - NoDocTemplateEntity (created in makeTemplate)
*/
def createLazyTemplateMember(aSym: Symbol, inTpl: DocTemplateImpl): MemberImpl = {
// Code is duplicate because the anonymous classes are created statically
def createNoDocMemberTemplate(bSym: Symbol, inTpl: DocTemplateImpl): NoDocTemplateMemberImpl = {
assert(modelFinished) // only created AFTER the model is finished
new NoDocTemplateMemberImpl(bSym, inTpl)
}
assert(modelFinished)
val bSym = normalizeTemplate(aSym)
if (docTemplatesCache isDefinedAt bSym)
docTemplatesCache(bSym)
else
docTemplatesCache.get(bSym.owner) match {
case Some(inTpl) =>
val mbrs = inTpl.members.collect({ case mbr: MemberImpl if mbr.sym == bSym => mbr })
assert(mbrs.length == 1)
mbrs.head
case _ =>
// move the class completely to the new location
createNoDocMemberTemplate(bSym, inTpl)
}
}
}
/** Get the root package */
def makeRootPackage: PackageImpl = docTemplatesCache(RootPackage).asInstanceOf[PackageImpl]
// TODO: Should be able to override the type
def makeMember(aSym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl): List[MemberImpl] = {
def makeMember0(bSym: Symbol, _useCaseOf: Option[MemberImpl]): Option[MemberImpl] = {
if (bSym.isGetter && bSym.isLazy)
Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val {
override lazy val comment = // The analyser does not duplicate the lazy val's DocDef when it introduces its accessor.
thisFactory.comment(bSym.accessed, inTpl.asInstanceOf[DocTemplateImpl]) // 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, implConv, inTpl) with Val {
override def isVar = true
override def useCaseOf = _useCaseOf
})
else if (bSym.isMethod && !bSym.hasAccessorFlag && !bSym.isConstructor && !bSym.isModule) {
val cSym = { // This unsightly hack closes issue #4086.
if (bSym == definitions.Object_synchronized) {
val cSymInfo = (bSym.info: @unchecked) match {
case PolyType(ts, MethodType(List(bp), mt)) =>
val cp = bp.cloneSymbol.setPos(bp.pos).setInfo(definitions.byNameType(bp.info))
PolyType(ts, MethodType(List(cp), mt))
}
bSym.cloneSymbol.setPos(bSym.pos).setInfo(cSymInfo)
}
else bSym
}
Some(new NonTemplateParamMemberImpl(cSym, implConv, inTpl) with HigherKindedImpl with Def {
override def isDef = true
override def useCaseOf = _useCaseOf
})
}
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, implConv, inTpl) with Val {
override def isVal = true
override def useCaseOf = _useCaseOf
})
else if (bSym.isAbstractType)
Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with TypeBoundsImpl with HigherKindedImpl with AbstractType {
override def isAbstractType = true
override def useCaseOf = _useCaseOf
})
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 && !modelFinished)
inTpl match {
case inPkg: PackageImpl => modelCreation.createTemplate(bSym, inTpl) match {
case p: PackageImpl if droppedPackages contains p => None
case p: PackageImpl => Some(p)
case _ => sys.error("'" + bSym + "' must be a package")
}
case _ =>
sys.error("'" + bSym + "' must be in a package")
}
else if (!modelFinished && templateShouldDocument(bSym, inTpl) && inOriginalOnwer(bSym, inTpl))
Some(modelCreation.createTemplate(bSym, inTpl))
else
None
}
if (!localShouldDocument(aSym) || aSym.isModuleClass || aSym.isPackageObject || aSym.isMixinConstructor)
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
bSym
}
val member = makeMember0(aSym, None)
if (allSyms.isEmpty)
member.toList
else
// Use cases replace the original definitions - SI-5054
allSyms flatMap { makeMember0(_, member) }
}
}
def findMember(aSym: Symbol, inTpl: DocTemplateImpl): Option[MemberImpl] = {
val tplSym = normalizeTemplate(aSym.owner)
inTpl.members.find(_.sym == aSym)
}
def findTemplate(query: String): Option[DocTemplateImpl] = {
assert(modelFinished)
docTemplatesCache.values find { (tpl: TemplateImpl) => tpl.qualifiedName == query && !tpl.isObject }
}
def findTemplateMaybe(aSym: Symbol): Option[DocTemplateImpl] = {
assert(modelFinished)
docTemplatesCache.get(normalizeTemplate(aSym))
}
def makeTemplate(aSym: Symbol): TemplateImpl = makeTemplate(aSym, None)
def makeTemplate(aSym: Symbol, inTpl: Option[TemplateImpl]): TemplateImpl = {
assert(modelFinished)
def makeNoDocTemplate(aSym: Symbol, inTpl: TemplateImpl): NoDocTemplateImpl = {
val bSym = normalizeTemplate(aSym)
noDocTemplatesCache.get(bSym) match {
case Some(noDocTpl) => noDocTpl
case None => new NoDocTemplateImpl(bSym, inTpl)
}
}
findTemplateMaybe(aSym) match {
case Some(dtpl) =>
dtpl
case None =>
val bSym = normalizeTemplate(aSym)
makeNoDocTemplate(bSym, if (inTpl.isDefined) inTpl.get else makeTemplate(bSym.owner))
}
}
/** */
def makeAnnotation(annot: AnnotationInfo): Annotation = {
val aSym = annot.symbol
new EntityImpl(aSym, makeTemplate(aSym.owner)) with Annotation {
lazy val annotationClass =
makeTemplate(annot.symbol)
val arguments = { // lazy
def noParams = annot.args map { _ => None }
val params: List[Option[ValueParam]] = annotationClass match {
case aClass: Class =>
(aClass.primaryConstructor map { _.valueParams.head }) match {
case Some(vps) => vps map { Some(_) }
case None => noParams
}
case _ => noParams
}
assert(params.length == annot.args.length)
(params zip annot.args) flatMap { case (param, arg) =>
makeTree(arg) match {
case Some(tree) =>
Some(new ValueArgument {
def parameter = param
def value = tree
})
case None => None
}
}
}
}
}
/** */
def makeTypeParam(aSym: Symbol, inTpl: TemplateImpl): TypeParam =
new ParameterImpl(aSym, inTpl) with TypeBoundsImpl with HigherKindedImpl with TypeParam {
def variance: String = {
if (sym hasFlag Flags.COVARIANT) "+"
else if (sym hasFlag Flags.CONTRAVARIANT) "-"
else ""
}
}
/** */
def makeValueParam(aSym: Symbol, inTpl: DocTemplateImpl): ValueParam = {
makeValueParam(aSym, inTpl, aSym.nameString)
}
/** */
def makeValueParam(aSym: Symbol, inTpl: DocTemplateImpl, newName: String): ValueParam =
new ParameterImpl(aSym, inTpl) with ValueParam {
override val name = newName
def defaultValue =
if (aSym.hasDefault) {
// units.filter should return only one element
(currentRun.units filter (_.source.file == aSym.sourceFile)).toList match {
case List(unit) =>
(unit.body find (_.symbol == aSym)) match {
case Some(ValDef(_,_,_,rhs)) => makeTree(rhs)
case _ => None
}
case _ => None
}
}
else None
def resultType =
makeTypeInTemplateContext(aSym.tpe, inTpl, aSym)
def isImplicit = aSym.isImplicit
}
/** */
def makeTypeInTemplateContext(aType: Type, inTpl: TemplateImpl, dclSym: Symbol): TypeEntity = {
def ownerTpl(sym: Symbol): Symbol =
if (sym.isClass || sym.isModule || sym == NoSymbol) sym else ownerTpl(sym.owner)
val tpe =
if (thisFactory.settings.useStupidTypes.value) aType else {
def ownerTpl(sym: Symbol): Symbol =
if (sym.isClass || sym.isModule || sym == NoSymbol) sym else ownerTpl(sym.owner)
val fixedSym = if (inTpl.sym.isModule) inTpl.sym.moduleClass else inTpl.sym
aType.asSeenFrom(fixedSym.thisType, ownerTpl(dclSym))
}
makeType(tpe, inTpl)
}
/** Get the types of the parents of the current class, ignoring the refinements */
def makeParentTypes(aType: Type, tpl: Option[DocTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match {
case RefinedType(parents, defs) =>
val ignoreParents = Set[Symbol](AnyClass, AnyRefClass, ObjectClass)
val filtParents =
// we don't want to expose too many links to AnyRef, that will just be redundant information
if (tpl.isDefined && { val sym = tpl.get.sym; (!sym.isModule && parents.length < 2) || (sym == AnyValClass) || (sym == AnyRefClass) || (sym == AnyClass) })
parents
else
parents.filterNot((p: Type) => ignoreParents(p.typeSymbol))
/** Returns:
* - a DocTemplate if the type's symbol is documented
* - a NoDocTemplateMember if the type's symbol is not documented in its parent but in another template
* - a NoDocTemplate if the type's symbol is not documented at all */
def makeTemplateOrMemberTemplate(parent: Type): TemplateImpl = {
def noDocTemplate = makeTemplate(parent.typeSymbol)
findTemplateMaybe(parent.typeSymbol) match {
case Some(tpl) => tpl
case None => parent match {
case TypeRef(pre, sym, args) =>
findTemplateMaybe(pre.typeSymbol) match {
case Some(tpl) => findMember(parent.typeSymbol, tpl).collect({case t: TemplateImpl => t}).getOrElse(noDocTemplate)
case None => noDocTemplate
}
case _ => noDocTemplate
}
}
}
filtParents.map(parent => {
val templateEntity = makeTemplateOrMemberTemplate(parent)
val typeEntity = makeType(parent, inTpl)
(templateEntity, typeEntity)
})
case _ =>
List((makeTemplate(aType.typeSymbol), makeType(aType, inTpl)))
}
/** */
def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = {
def templatePackage = closestPackage(inTpl.sym)
def createTypeEntity = new TypeEntity {
private val nameBuffer = new StringBuilder
private var refBuffer = new immutable.TreeMap[Int, (LinkTo, Int)]
private def appendTypes0(types: List[Type], sep: String): Unit = types match {
case Nil =>
case tp :: Nil =>
appendType0(tp)
case tp :: tps =>
appendType0(tp)
nameBuffer append sep
appendTypes0(tps, sep)
}
private def appendType0(tpe: Type): Unit = tpe match {
/* Type refs */
case tp: TypeRef if definitions.isFunctionType(tp) =>
val args = tp.normalize.typeArgs
nameBuffer append '('
appendTypes0(args.init, ", ")
nameBuffer append ") ⇒ "
appendType0(args.last)
case tp: TypeRef if definitions.isScalaRepeatedParamType(tp) =>
appendType0(tp.args.head)
nameBuffer append '*'
case tp: TypeRef if definitions.isByNameParamType(tp) =>
nameBuffer append "⇒ "
appendType0(tp.args.head)
case tp: TypeRef if definitions.isTupleType(tp) =>
val args = tp.normalize.typeArgs
nameBuffer append '('
appendTypes0(args, ", ")
nameBuffer append ')'
case TypeRef(pre, aSym, targs) =>
val preSym = pre.widen.typeSymbol
// There's a work in progress here trying to deal with the
// places where undesirable prefixes are printed.
// ...
// If the prefix is something worthy of printing, see if the prefix type
// is in the same package as the enclosing template. If so, print it
// unqualified and they'll figure it out.
//
// val stripPrefixes = List(templatePackage.fullName + ".", "package.", "java.lang.")
// if (!preSym.printWithoutPrefix) {
// nameBuffer append stripPrefixes.foldLeft(pre.prefixString)(_ stripPrefix _)
// }
// SI-3314/SI-4888: Classes, Traits and Types can be inherited from a template to another:
// class Enum { abstract class Value }
// class Day extends Enum { object Mon extends Value /*...*/ }
// ===> in such cases we have two options:
// (0) if there's no inheritance taking place (Enum#Value) we can link to the template directly
// (1) if we generate the doc template for Day, we can link to the correct member
// (2) if we don't generate the doc template, we should at least indicate the correct prefix in the tooltip
val bSym = normalizeTemplate(aSym)
val owner =
if ((preSym != NoSymbol) && /* it needs a prefix */
(preSym != bSym.owner) && /* prefix is different from owner */
// ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */
// (preSym != inTpl.sym.moduleClass)) && /* or object */
(aSym == bSym)) /* normalization doesn't play tricks on us */
preSym
else
bSym.owner
val bTpl = findTemplateMaybe(bSym)
val link =
if (owner == bSym.owner && bTpl.isDefined)
// (0) the owner's class is linked AND has a template - lovely
LinkToTpl(bTpl.get)
else {
val oTpl = findTemplateMaybe(owner)
val bMbr = oTpl.map(findMember(bSym, _))
if (oTpl.isDefined && bMbr.isDefined && bMbr.get.isDefined)
// (1) the owner's class
LinkToMember(bMbr.get.get, oTpl.get) //ugh
else
// (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!)
LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner))))
}
// TODO: The name might include a prefix, take care of that!
val name = bSym.nameString
val pos0 = nameBuffer.length
refBuffer += pos0 -> ((link, name.length))
nameBuffer append name
// if (bSym.isNonClassType && bSym != AnyRefClass) {
// nameBuffer append bSym.decodedName
// } else {
// val tpl = makeTemplate(bSym)
// val pos0 = nameBuffer.length
// refBuffer += pos0 -> ((LinkToTpl(tpl), tpl.name.length))
// nameBuffer append tpl.name
// }
if (!targs.isEmpty) {
nameBuffer append '['
appendTypes0(targs, ", ")
nameBuffer append ']'
}
/* Refined types */
case RefinedType(parents, defs) =>
val ignoreParents = Set[Symbol](AnyClass, ObjectClass)
val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) match {
case Nil => parents
case ps => ps
}
appendTypes0(filtParents, " with ")
// XXX Still todo: properly printing refinements.
// Since I didn't know how to go about displaying a multi-line type, I went with
// printing single method refinements (which should be the most common) and printing
// the number of members if there are more.
defs.toList match {
case Nil => ()
case x :: Nil => nameBuffer append (" { " + x.defString + " }")
case xs => nameBuffer append (" { ... /* %d definitions in type refinement */ }" format xs.size)
}
/* Eval-by-name types */
case NullaryMethodType(result) =>
nameBuffer append '⇒'
appendType0(result)
/* Polymorphic types */
case PolyType(tparams, result) => assert(tparams.nonEmpty)
// throw new Error("Polymorphic type '" + tpe + "' cannot be printed as a type")
def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else
tps.map{tparam =>
tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams)
}.mkString("[", ", ", "]")
nameBuffer append typeParamsToString(tparams)
appendType0(result)
case tpen =>
nameBuffer append tpen.toString
}
appendType0(aType)
val refEntity = refBuffer
val name = optimize(nameBuffer.toString)
}
if (aType.isTrivial)
typeCache.get(aType) match {
case Some(typeEntity) => typeEntity
case None =>
val typeEntity = createTypeEntity
typeCache += aType -> typeEntity
typeEntity
}
else
createTypeEntity
}
def normalizeOwner(aSym: Symbol): Symbol =
/*
* Okay, here's the explanation of what happens. The code:
*
* package foo {
* object `package` {
* class Bar
* }
* }
*
* will yield this Symbol structure:
*
* +---------------+ +--------------------------+
* | package foo#1 ----(1)---> module class foo#2 |
* +---------------+ | +----------------------+ | +-------------------------+
* | | package object foo#3 ------(1)---> module class package#4 |
* | +----------------------+ | | +---------------------+ |
* +--------------------------+ | | class package$Bar#5 | |
* | +---------------------+ |
* +-------------------------+
* (1) sourceModule
* (2) you get out of owners with .owner
*/
normalizeTemplate(aSym)
def inOriginalOnwer(aSym: Symbol, inTpl: TemplateImpl): Boolean =
normalizeOwner(aSym.owner) == normalizeOwner(inTpl.sym)
def templateShouldDocument(aSym: Symbol, inTpl: TemplateImpl): Boolean =
(aSym.isClass || aSym.isModule || aSym == AnyRefClass) &&
localShouldDocument(aSym) &&
!isEmptyJavaObject(aSym) &&
// either it's inside the original owner or we can document it later:
(!inOriginalOnwer(aSym, inTpl) || (aSym.isPackageClass || (aSym.sourceFile != null)))
def membersShouldDocument(sym: Symbol, inTpl: TemplateImpl) =
// pruning modules that shouldn't be documented
// Why Symbol.isInitialized? Well, because we need to avoid exploring all the space available to scaladoc
// from the classpath -- scaladoc is a hog, it will explore everything starting from the root package unless we
// somehow prune the tree. And isInitialized is a good heuristic for prunning -- if the package was not explored
// during typer and refchecks, it's not necessary for the current application and there's no need to explore it.
(!sym.isModule || sym.moduleClass.isInitialized) &&
// documenting only public and protected members
localShouldDocument(sym) &&
// Only this class's constructors are part of its members, inherited constructors are not.
(!sym.isConstructor || sym.owner == inTpl.sym) &&
// If the @bridge annotation overrides a normal member, show it
!isPureBridge(sym)
def isEmptyJavaObject(aSym: Symbol): Boolean =
aSym.isModule && aSym.isJavaDefined &&
aSym.info.members.exists(s => localShouldDocument(s) && (!s.isConstructor || s.owner == aSym))
def localShouldDocument(aSym: Symbol): Boolean =
!aSym.isPrivate && (aSym.isProtected || aSym.privateWithin == NoSymbol) && !aSym.isSynthetic
/** Filter '@bridge' methods only if *they don't override non-bridge methods*. See SI-5373 for details */
def isPureBridge(sym: Symbol) = sym.isBridge && sym.allOverriddenSymbols.forall(_.isBridge)
// the classes that are excluded from the index should also be excluded from the diagrams
def classExcluded(clazz: TemplateEntity): Boolean = settings.hardcoded.isExcluded(clazz.qualifiedName)
// the implicit conversions that are excluded from the pages should not appear in the diagram
def implicitExcluded(convertorMethod: String): Boolean = settings.hardcoded.commonConversionTargets.contains(convertorMethod)
}