scala.tools.nsc.doc.model.ModelFactoryTypeSupport.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
/* NSC -- new Scala compiler -- Copyright 2007-2013 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 */
trait ModelFactoryTypeSupport {
thisFactory: ModelFactory
with ModelFactoryImplicitSupport
with ModelFactoryTypeSupport
with DiagramFactory
with CommentFactory
with TreeFactory =>
import global._
import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass }
import rootMirror.{ RootPackage, RootClass, EmptyPackage }
protected val typeCache = new mutable.LinkedHashMap[Type, TypeEntity]
/** */
def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = {
def templatePackage = closestPackage(inTpl.sym)
def createTypeEntity = new TypeEntity {
private var 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
// 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 the symbol comes from an external library for which we know the documentation URL, point to it.
// (3) 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 */
(aSym == bSym)) /* normalization doesn't play tricks on us */
preSym
else
bSym.owner
val link =
findTemplateMaybe(bSym) match {
case Some(bTpl) if owner == bSym.owner =>
// (0) the owner's class is linked AND has a template - lovely
LinkToTpl(bTpl)
case _ =>
val oTpl = findTemplateMaybe(owner)
(oTpl, oTpl flatMap (findMember(bSym, _))) match {
case (Some(oTpl), Some(bMbr)) =>
// (1) the owner's class
LinkToMember(bMbr, oTpl)
case _ =>
val name = makeQualifiedName(bSym)
if (!bSym.owner.isPackage)
Tooltip(name)
else
findExternalLink(name).getOrElse (
// (3) if we couldn't find neither the owner nor external URL to link to, show a tooltip with the qualified name
Tooltip(name)
)
}
}
// SI-4360 Showing prefixes when necessary
// We check whether there's any directly accessible type with the same name in the current template OR if the
// type is inherited from one template to another. There may be multiple symbols with the same name in scope,
// but we won't show the prefix if our symbol is among them, only if *it's not* -- that's equal to showing
// the prefix only for ambiguous references, not for overloaded ones.
def needsPrefix: Boolean = {
if ((owner != bSym.owner || preSym.isRefinementClass) && (normalizeTemplate(owner) != inTpl.sym))
return true
// don't get tricked into prefixng method type params and existentials:
// I tried several tricks BUT adding the method for which I'm creating the type => that simply won't scale,
// as ValueParams are independent of their parent member, and I really don't want to add this information to
// all terms, as we're already over the allowed memory footprint
if (aSym.isTypeParameterOrSkolem || aSym.isExistentiallyBound /* existential or existential skolem */)
return false
for (tpl <- inTpl.sym.ownerChain) {
tpl.info.member(bSym.name) match {
case NoSymbol =>
// No syms with that name, look further inside the owner chain
case sym =>
// Symbol found -- either the correct symbol, another one OR an overloaded alternative
if (sym == bSym)
return false
else sym.info match {
case OverloadedType(owner, alternatives) =>
return alternatives.contains(bSym)
case _ =>
return true
}
}
}
// if it's not found in the owner chain, we can safely leave out the prefix
false
}
val prefix =
if (!settings.docNoPrefixes.value && needsPrefix && (bSym != AnyRefClass /* which we normalize */)) {
if (!owner.isRefinementClass) {
val qName = makeQualifiedName(owner, Some(inTpl.sym))
if (qName != "") qName + "." else ""
}
else {
nameBuffer append "("
appendType0(pre)
nameBuffer append ")#"
"" // we already appended the prefix
}
} else ""
//DEBUGGING:
//if (makeQualifiedName(bSym) == "pack1.A") println("needsPrefix(" + bSym + ", " + owner + ", " + inTpl.qualifiedName + ") => " + needsPrefix + " and prefix=" + prefix)
val name = prefix + bSym.nameString
val pos0 = nameBuffer.length
refBuffer += pos0 -> ((link, name.length))
nameBuffer append 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)
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 et@ExistentialType(quantified, underlying) =>
def appendInfoStringReduced(sym: Symbol, tp: Type): Unit = {
if (sym.isType && !sym.isAliasType && !sym.isClass) {
tp match {
case PolyType(tparams, _) =>
nameBuffer append "["
appendTypes0(tparams.map(_.tpe), ", ")
nameBuffer append "]"
case _ =>
}
tp.resultType match {
case rt @ TypeBounds(_, _) =>
appendType0(rt)
case rt =>
nameBuffer append " <: "
appendType0(rt)
}
} else {
// fallback to the Symbol infoString
nameBuffer append sym.infoString(tp)
}
}
def appendClauses = {
nameBuffer append " forSome {"
var first = true
val qset = quantified.toSet
for (sym <- quantified) {
if (!first) { nameBuffer append ", " } else first = false
if (sym.isSingletonExistential) {
nameBuffer append "val "
nameBuffer append tpnme.dropSingletonName(sym.name)
nameBuffer append ": "
appendType0(dropSingletonType(sym.info.bounds.hi))
} else {
if (sym.flagString != "") nameBuffer append (sym.flagString + " ")
if (sym.keyString != "") nameBuffer append (sym.keyString + " ")
nameBuffer append sym.varianceString
nameBuffer append sym.nameString
appendInfoStringReduced(sym, sym.info)
}
}
nameBuffer append "}"
}
underlying match {
case TypeRef(pre, sym, args) if et.isRepresentableWithWildcards =>
appendType0(typeRef(pre, sym, Nil))
nameBuffer append "["
var first = true
val qset = quantified.toSet
for (arg <- args) {
if (!first) { nameBuffer append ", " } else first = false
arg match {
case TypeRef(_, sym, _) if (qset contains sym) =>
nameBuffer append "_"
appendInfoStringReduced(sym, sym.info)
case arg =>
appendType0(arg)
}
}
nameBuffer append "]"
case MethodType(_, _) | NullaryMethodType(_) | PolyType(_, _) =>
nameBuffer append "("
appendType0(underlying)
nameBuffer append ")"
appendClauses
case _ =>
appendType0(underlying)
appendClauses
}
case tb@TypeBounds(lo, hi) =>
if (tb.lo != TypeBounds.empty.lo) {
nameBuffer append " >: "
appendType0(lo)
}
if (tb.hi != TypeBounds.empty.hi) {
nameBuffer append " <: "
appendType0(hi)
}
// case tpen: ThisType | SingleType | SuperType =>
// if (tpen.isInstanceOf[ThisType] && tpen.asInstanceOf[ThisType].sym.isEffectiveRoot) {
// appendType0 typeRef(NoPrefix, sym, Nil)
// } else {
// val underlying =
// val pre = underlying.typeSymbol.skipPackageObject
// if (pre.isOmittablePrefix) pre.fullName + ".type"
// else prefixString + "type"
case tpen@ThisType(sym) =>
appendType0(typeRef(NoPrefix, sym, Nil))
nameBuffer append ".this"
if (!tpen.underlying.typeSymbol.skipPackageObject.isOmittablePrefix) nameBuffer append ".type"
case tpen@SuperType(thistpe, supertpe) =>
nameBuffer append "super["
appendType0(supertpe)
nameBuffer append "]"
case tpen@SingleType(pre, sym) =>
appendType0(typeRef(pre, sym, Nil))
if (!tpen.underlying.typeSymbol.skipPackageObject.isOmittablePrefix) nameBuffer append ".type"
case tpen =>
nameBuffer append tpen.toString
}
appendType0(aType)
val refEntity = refBuffer
val name = optimize(nameBuffer.toString)
nameBuffer = null
}
// SI-4360: Entity caching depends on both the type AND the template it's in, as the prefixes might change for the
// same type based on the template the type is shown in.
if (settings.docNoPrefixes.value)
typeCache.getOrElseUpdate(aType, createTypeEntity)
else createTypeEntity
}
}