mirror of https://github.com/sbt/sbt.git
Move API extraction logic to a separate class.
This way we have a little bit more clear separation between compiler phase logic and the core logic responsible for processing each compilation unit and extracting an api for it. As added benefit, we have a little bit less of mutable state (e.g. sourceFile doesn't need to be a var anymore). The API extraction logic contains some internal caches that are required for correctness. It wasn't very clear if they have to be maintained during entire phase run or just during single compilation unit processing. It looks like they have to be maintained during single compilation unit processing and refactored code both documents that contracts and implements it in the API phase.
This commit is contained in:
parent
7d4cf7b8ab
commit
beea6a9b4a
|
|
@ -20,7 +20,7 @@ object API
|
|||
final class API(val global: CallbackGlobal) extends Compat
|
||||
{
|
||||
import global._
|
||||
def error(msg: String) = throw new RuntimeException(msg)
|
||||
|
||||
@inline def debug(msg: => String) = if(settings.verbose.value) inform(msg)
|
||||
|
||||
def newPhase(prev: Phase) = new ApiPhase(prev)
|
||||
|
|
@ -39,393 +39,25 @@ final class API(val global: CallbackGlobal) extends Compat
|
|||
def processScalaUnit(unit: CompilationUnit)
|
||||
{
|
||||
val sourceFile = unit.source.file.file
|
||||
currentSourceFile = sourceFile
|
||||
debug("Traversing " + sourceFile)
|
||||
val traverser = new TopLevelHandler(sourceFile)
|
||||
val extractApi = new ExtractAPI[global.type](global, sourceFile)
|
||||
val traverser = new TopLevelHandler(extractApi)
|
||||
traverser.apply(unit.body)
|
||||
val packages = traverser.packages.toArray[String].map(p => new xsbti.api.Package(p))
|
||||
val source = new xsbti.api.SourceAPI(packages, traverser.definitions.toArray[xsbti.api.Definition])
|
||||
forceStructures()
|
||||
clearCaches()
|
||||
extractApi.forceStructures()
|
||||
callback.api(sourceFile, source)
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks the source file associated with the CompilationUnit currently being processed by the API phase.
|
||||
// This is used when recording inheritance dependencies.
|
||||
private[this] var currentSourceFile: File = _
|
||||
|
||||
// this cache reduces duplicate work both here and when persisting
|
||||
// caches on other structures had minimal effect on time and cache size
|
||||
// (tried: Definition, Modifier, Path, Id, String)
|
||||
private[this] val typeCache = new HashMap[(Symbol,Type), xsbti.api.Type]
|
||||
// these caches are necessary for correctness
|
||||
private[this] val structureCache = new HashMap[Symbol, xsbti.api.Structure]
|
||||
private[this] val classLikeCache = new HashMap[(Symbol,Symbol), xsbti.api.ClassLike]
|
||||
private[this] val pending = new HashSet[xsbti.api.Lazy[_]]
|
||||
|
||||
private[this] val emptyStringArray = new Array[String](0)
|
||||
|
||||
// to mitigate "temporary leaks" like that caused by NoPhase in 2.8.0,
|
||||
// this ensures this class is not retaining objects
|
||||
private def clearCaches()
|
||||
{
|
||||
typeCache.clear()
|
||||
structureCache.clear()
|
||||
classLikeCache.clear()
|
||||
}
|
||||
|
||||
// call back to the xsbti.SafeLazy class in main sbt code to construct a SafeLazy instance
|
||||
// we pass a thunk, whose class is loaded by the interface class loader (this class's loader)
|
||||
// SafeLazy ensures that once the value is forced, the thunk is nulled out and so
|
||||
// references to the thunk's classes are not retained. Specifically, it allows the interface classes
|
||||
// (those in this subproject) to be garbage collected after compilation.
|
||||
private[this] val safeLazy = Class.forName("xsbti.SafeLazy").getMethod("apply", classOf[xsbti.F0[_]])
|
||||
private def lzy[S <: AnyRef](s: => S): xsbti.api.Lazy[S] =
|
||||
{
|
||||
val z = safeLazy.invoke(null, Message(s)).asInstanceOf[xsbti.api.Lazy[S]]
|
||||
pending += z
|
||||
z
|
||||
}
|
||||
|
||||
// force all lazy structures. This is necessary so that we see the symbols/types at this phase and
|
||||
// so that we don't hold on to compiler objects and classes
|
||||
private def forceStructures(): Unit =
|
||||
if(pending.isEmpty)
|
||||
structureCache.clear()
|
||||
else
|
||||
{
|
||||
val toProcess = pending.toList
|
||||
pending.clear()
|
||||
toProcess foreach { _.get() }
|
||||
forceStructures()
|
||||
}
|
||||
|
||||
private def thisPath(sym: Symbol) = path(pathComponents(sym, Constants.thisPath :: Nil))
|
||||
private def path(components: List[PathComponent]) = new xsbti.api.Path(components.toArray[PathComponent])
|
||||
private def pathComponents(sym: Symbol, postfix: List[PathComponent]): List[PathComponent] =
|
||||
{
|
||||
if(sym == NoSymbol || sym.isRoot || sym.isEmptyPackageClass || sym.isRootPackage) postfix
|
||||
else pathComponents(sym.owner, new xsbti.api.Id(simpleName(sym)) :: postfix)
|
||||
}
|
||||
private def simpleType(in: Symbol, t: Type): SimpleType =
|
||||
processType(in, t) match
|
||||
{
|
||||
case s: SimpleType => s
|
||||
case x => warning("Not a simple type:\n\tType: " + t + " (" + t.getClass + ")\n\tTransformed: " + x.getClass); Constants.emptyType
|
||||
}
|
||||
private def types(in: Symbol, t: List[Type]): Array[xsbti.api.Type] = t.toArray[Type].map(processType(in, _))
|
||||
private def projectionType(in: Symbol, pre: Type, sym: Symbol) =
|
||||
{
|
||||
if(pre == NoPrefix)
|
||||
{
|
||||
if(sym.isLocalClass || sym.isRoot || sym.isRootPackage) Constants.emptyType
|
||||
else if(sym.isTypeParameterOrSkolem || sym.isExistentiallyBound) reference(sym)
|
||||
else {
|
||||
// this appears to come from an existential type in an inherited member- not sure why isExistential is false here
|
||||
/*println("Warning: Unknown prefixless type: " + sym + " in " + sym.owner + " in " + sym.enclClass)
|
||||
println("\tFlags: " + sym.flags + ", istype: " + sym.isType + ", absT: " + sym.isAbstractType + ", alias: " + sym.isAliasType + ", nonclass: " + isNonClassType(sym))*/
|
||||
reference(sym)
|
||||
}
|
||||
}
|
||||
else if(sym.isRoot || sym.isRootPackage) Constants.emptyType
|
||||
else new xsbti.api.Projection(simpleType(in, pre), simpleName(sym))
|
||||
}
|
||||
private def reference(sym: Symbol): xsbti.api.ParameterRef = new xsbti.api.ParameterRef(tparamID(sym))
|
||||
|
||||
private def annotations(in: Symbol, as: List[AnnotationInfo]): Array[xsbti.api.Annotation] = as.toArray[AnnotationInfo].map(annotation(in,_))
|
||||
private def annotation(in: Symbol, a: AnnotationInfo) =
|
||||
new xsbti.api.Annotation(processType(in, a.atp),
|
||||
if(a.assocs.isEmpty) Array(new xsbti.api.AnnotationArgument("", a.args.mkString("(", ",", ")"))) // what else to do with a Tree?
|
||||
else a.assocs.map { case (name, value) => new xsbti.api.AnnotationArgument(name.toString, value.toString) }.toArray[xsbti.api.AnnotationArgument]
|
||||
)
|
||||
private def annotated(in: Symbol, as: List[AnnotationInfo], tpe: Type) = new xsbti.api.Annotated(processType(in, tpe), annotations(in, as))
|
||||
|
||||
private def viewer(s: Symbol) = (if(s.isModule) s.moduleClass else s).thisType
|
||||
private def printMember(label: String, in: Symbol, t: Type) = println(label + " in " + in + " : " + t + " (debug: " + debugString(t) + " )")
|
||||
private def defDef(in: Symbol, s: Symbol) =
|
||||
{
|
||||
def build(t: Type, typeParams: Array[xsbti.api.TypeParameter], valueParameters: List[xsbti.api.ParameterList]): xsbti.api.Def =
|
||||
{
|
||||
def parameterList(syms: List[Symbol]): xsbti.api.ParameterList =
|
||||
{
|
||||
val isImplicitList = syms match { case head :: _ => isImplicit(head); case _ => false }
|
||||
new xsbti.api.ParameterList(syms.map(parameterS).toArray, isImplicitList)
|
||||
}
|
||||
t match
|
||||
{
|
||||
case PolyType(typeParams0, base) =>
|
||||
assert(typeParams.isEmpty)
|
||||
assert(valueParameters.isEmpty)
|
||||
build(base, typeParameters(in, typeParams0), Nil)
|
||||
case MethodType(params, resultType) =>
|
||||
build(resultType, typeParams, parameterList(params) :: valueParameters)
|
||||
case Nullary(resultType) => // 2.9 and later
|
||||
build(resultType, typeParams, valueParameters)
|
||||
case returnType =>
|
||||
val t2 = processType(in, dropConst(returnType))
|
||||
new xsbti.api.Def(valueParameters.reverse.toArray, t2, typeParams, simpleName(s), getAccess(s), getModifiers(s), annotations(in,s))
|
||||
}
|
||||
}
|
||||
def parameterS(s: Symbol): xsbti.api.MethodParameter =
|
||||
makeParameter(simpleName(s), s.info, s.info.typeSymbol, s)
|
||||
|
||||
// paramSym is only for 2.8 and is to determine if the parameter has a default
|
||||
def makeParameter(name: String, tpe: Type, ts: Symbol, paramSym: Symbol): xsbti.api.MethodParameter =
|
||||
{
|
||||
import xsbti.api.ParameterModifier._
|
||||
val (t, special) =
|
||||
if(ts == definitions.RepeatedParamClass)// || s == definitions.JavaRepeatedParamClass)
|
||||
(tpe.typeArgs(0), Repeated)
|
||||
else if(ts == definitions.ByNameParamClass)
|
||||
(tpe.typeArgs(0), ByName)
|
||||
else
|
||||
(tpe, Plain)
|
||||
new xsbti.api.MethodParameter(name, processType(in, t), hasDefault(paramSym), special)
|
||||
}
|
||||
val t = viewer(in).memberInfo(s)
|
||||
build(t, Array(), Nil)
|
||||
}
|
||||
private def hasDefault(s: Symbol) = s != NoSymbol && s.hasFlag(Flags.DEFAULTPARAM)
|
||||
private def fieldDef[T](in: Symbol, s: Symbol, keepConst: Boolean, create: (xsbti.api.Type, String, xsbti.api.Access, xsbti.api.Modifiers, Array[xsbti.api.Annotation]) => T): T =
|
||||
{
|
||||
val t = dropNullary(viewer(in).memberType(s))
|
||||
val t2 = if(keepConst) t else dropConst(t)
|
||||
create(processType(in, t2), simpleName(s), getAccess(s), getModifiers(s), annotations(in, s))
|
||||
}
|
||||
private def dropConst(t: Type): Type = t match {
|
||||
case ConstantType(constant) => constant.tpe
|
||||
case _ => t
|
||||
}
|
||||
private def dropNullary(t: Type): Type = t match {
|
||||
case Nullary(un) => un
|
||||
case _ => t
|
||||
}
|
||||
|
||||
private def typeDef(in: Symbol, s: Symbol): xsbti.api.TypeMember =
|
||||
{
|
||||
val (typeParams, tpe) =
|
||||
viewer(in).memberInfo(s) match
|
||||
{
|
||||
case PolyType(typeParams0, base) => (typeParameters(in, typeParams0), base)
|
||||
case t => (Array[xsbti.api.TypeParameter](), t)
|
||||
}
|
||||
val name = simpleName(s)
|
||||
val access = getAccess(s)
|
||||
val modifiers = getModifiers(s)
|
||||
val as = annotations(in, s)
|
||||
|
||||
if(s.isAliasType)
|
||||
new xsbti.api.TypeAlias(processType(in, tpe), typeParams, name, access, modifiers, as)
|
||||
else if(s.isAbstractType)
|
||||
{
|
||||
val bounds = tpe.bounds
|
||||
new xsbti.api.TypeDeclaration(processType(in, bounds.lo), processType(in, bounds.hi), typeParams, name, access, modifiers, as)
|
||||
}
|
||||
else
|
||||
error("Unknown type member" + s)
|
||||
}
|
||||
|
||||
private def structure(in: Symbol, s: Symbol): xsbti.api.Structure = structure(viewer(in).memberInfo(s), s, true)
|
||||
private def structure(info: Type): xsbti.api.Structure = structure(info, info.typeSymbol, false)
|
||||
private def structure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure =
|
||||
structureCache.getOrElseUpdate( s, mkStructure(info, s, inherit))
|
||||
|
||||
private def removeConstructors(ds: List[Symbol]): List[Symbol] = ds filter { !_.isConstructor}
|
||||
|
||||
private def mkStructure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure =
|
||||
{
|
||||
val (declared, inherited) = info.members.reverse.partition(_.owner == s)
|
||||
val baseTypes = info.baseClasses.tail.map(info.baseType)
|
||||
val ds = if(s.isModuleClass) removeConstructors(declared) else declared
|
||||
val is = if(inherit) removeConstructors(inherited) else Nil
|
||||
mkStructure(s, baseTypes, ds, is)
|
||||
}
|
||||
|
||||
// If true, this template is publicly visible and should be processed as a public inheritance dependency.
|
||||
// Local classes and local refinements will never be traversed by the api phase, so we don't need to check for that.
|
||||
private[this] def isPublicStructure(s: Symbol): Boolean =
|
||||
s.isStructuralRefinement ||
|
||||
// do not consider templates that are private[this] or private
|
||||
!(s.isPrivate && (s.privateWithin == NoSymbol || s.isLocal))
|
||||
|
||||
private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure = {
|
||||
if(isPublicStructure(s))
|
||||
addInheritedDependencies(currentSourceFile, bases.map(_.dealias.typeSymbol))
|
||||
new xsbti.api.Structure(lzy(types(s, bases)), lzy(processDefinitions(s, declared)), lzy(processDefinitions(s, inherited)))
|
||||
}
|
||||
private def processDefinitions(in: Symbol, defs: List[Symbol]): Array[xsbti.api.Definition] =
|
||||
sort(defs.toArray).flatMap( (d: Symbol) => definition(in, d))
|
||||
private[this] def sort(defs: Array[Symbol]): Array[Symbol] = {
|
||||
Arrays.sort(defs, sortClasses)
|
||||
defs
|
||||
}
|
||||
|
||||
private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.Definition] =
|
||||
{
|
||||
def mkVar = Some(fieldDef(in, sym, false, new xsbti.api.Var(_,_,_,_,_)))
|
||||
def mkVal = Some(fieldDef(in, sym, true, new xsbti.api.Val(_,_,_,_,_)))
|
||||
if(isClass(sym))
|
||||
if(ignoreClass(sym)) None else Some(classLike(in, sym))
|
||||
else if(sym.isNonClassType)
|
||||
Some(typeDef(in, sym))
|
||||
else if(sym.isVariable)
|
||||
if(isSourceField(sym)) mkVar else None
|
||||
else if(sym.isStable)
|
||||
if(isSourceField(sym)) mkVal else None
|
||||
else if(sym.isSourceMethod && !sym.isSetter)
|
||||
if(sym.isGetter) mkVar else Some(defDef(in, sym))
|
||||
else
|
||||
None
|
||||
}
|
||||
private def ignoreClass(sym: Symbol): Boolean =
|
||||
sym.isLocalClass || sym.isAnonymousClass || sym.fullName.endsWith(LocalChild.toString)
|
||||
|
||||
// This filters private[this] vals/vars that were not in the original source.
|
||||
// The getter will be used for processing instead.
|
||||
private def isSourceField(sym: Symbol): Boolean =
|
||||
{
|
||||
val getter = sym.getter(sym.enclClass)
|
||||
// the check `getter eq sym` is a precaution against infinite recursion
|
||||
// `isParamAccessor` does not exist in all supported versions of Scala, so the flag check is done directly
|
||||
(getter == NoSymbol && !sym.hasFlag(Flags.PARAMACCESSOR)) || (getter eq sym)
|
||||
}
|
||||
private def getModifiers(s: Symbol): xsbti.api.Modifiers =
|
||||
{
|
||||
import Flags._
|
||||
val absOver = s.hasFlag(ABSOVERRIDE)
|
||||
val abs = s.hasFlag(ABSTRACT) || s.hasFlag(DEFERRED) || absOver
|
||||
val over = s.hasFlag(OVERRIDE) || absOver
|
||||
new xsbti.api.Modifiers(abs, over, s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY), hasMacro(s))
|
||||
}
|
||||
|
||||
private def isImplicit(s: Symbol) = s.hasFlag(Flags.IMPLICIT)
|
||||
private def getAccess(c: Symbol): xsbti.api.Access =
|
||||
{
|
||||
if(c.isPublic) Constants.public
|
||||
else if(c.isPrivateLocal) Constants.privateLocal
|
||||
else if(c.isProtectedLocal) Constants.protectedLocal
|
||||
else
|
||||
{
|
||||
val within = c.privateWithin
|
||||
val qualifier = if(within == NoSymbol) Constants.unqualified else new xsbti.api.IdQualifier(within.fullName)
|
||||
if(c.hasFlag(Flags.PROTECTED)) new xsbti.api.Protected(qualifier)
|
||||
else new xsbti.api.Private(qualifier)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all types that directly refer to the `forbidden` symbol by `NoType`.
|
||||
* (a specialized version of substThisAndSym)
|
||||
*/
|
||||
class SuppressSymbolRef(forbidden: Symbol) extends TypeMap {
|
||||
def apply(tp: Type) =
|
||||
if (tp.typeSymbolDirect == forbidden) NoType
|
||||
else mapOver(tp)
|
||||
}
|
||||
|
||||
private def processType(in: Symbol, t: Type): xsbti.api.Type = typeCache.getOrElseUpdate((in, t), makeType(in, t))
|
||||
private def makeType(in: Symbol, t: Type): xsbti.api.Type =
|
||||
{
|
||||
|
||||
val dealiased = t match {
|
||||
case TypeRef(_, sym, _) if sym.isAliasType => t.dealias
|
||||
case _ => t
|
||||
}
|
||||
|
||||
dealiased match
|
||||
{
|
||||
case NoPrefix => Constants.emptyType
|
||||
case ThisType(sym) => new xsbti.api.Singleton(thisPath(sym))
|
||||
case SingleType(pre, sym) => projectionType(in, pre, sym)
|
||||
case ConstantType(constant) => new xsbti.api.Constant(processType(in, constant.tpe), constant.stringValue)
|
||||
|
||||
/* explaining the special-casing of references to refinement classes (https://support.typesafe.com/tickets/1882)
|
||||
*
|
||||
* goal: a representation of type references to refinement classes that's stable across compilation runs
|
||||
* (and thus insensitive to typing from source or unpickling from bytecode)
|
||||
*
|
||||
* problem: the current representation, which corresponds to the owner chain of the refinement:
|
||||
* 1. is affected by pickling, so typing from source or using unpickled symbols give different results (because the unpickler "localizes" owners -- this could be fixed in the compiler)
|
||||
* 2. can't distinguish multiple refinements in the same owner (this is a limitation of SBT's internal representation and cannot be fixed in the compiler)
|
||||
*
|
||||
* potential solutions:
|
||||
* - simply drop the reference: won't work as collapsing all refinement types will cause recompilation to be skipped when a refinement is changed to another refinement
|
||||
* - represent the symbol in the api: can't think of a stable way of referring to an anonymous symbol whose owner changes when pickled
|
||||
* + expand the reference to the corresponding refinement type: doing that recursively may not terminate, but we can deal with that by approximating recursive references
|
||||
* (all we care about is being sound for recompilation: recompile iff a dependency changes, and this will happen as long as we have one unrolling of the reference to the refinement)
|
||||
*/
|
||||
case TypeRef(pre, sym, Nil) if sym.isRefinementClass =>
|
||||
// Since we only care about detecting changes reliably, we unroll a reference to a refinement class once.
|
||||
// Recursive references are simply replaced by NoType -- changes to the type will be seen in the first unrolling.
|
||||
// The API need not be type correct, so this truncation is acceptable. Most of all, the API should be compact.
|
||||
val unrolling = pre.memberInfo(sym) // this is a refinement type
|
||||
|
||||
// in case there are recursive references, suppress them -- does this ever happen?
|
||||
// we don't have a test case for this, so warn and hope we'll get a contribution for it :-)
|
||||
val withoutRecursiveRefs = new SuppressSymbolRef(sym).mapOver(unrolling)
|
||||
if (unrolling ne withoutRecursiveRefs)
|
||||
reporter.warning(sym.pos, "sbt-api: approximated refinement ref"+ t +" (== "+ unrolling +") to "+ withoutRecursiveRefs +"\nThis is currently untested, please report the code you were compiling.")
|
||||
|
||||
structure(withoutRecursiveRefs)
|
||||
case tr @ TypeRef(pre, sym, args) =>
|
||||
val base = projectionType(in, pre, sym)
|
||||
if(args.isEmpty)
|
||||
if(isRawType(tr))
|
||||
processType(in, rawToExistential(tr))
|
||||
else
|
||||
base
|
||||
else
|
||||
new xsbti.api.Parameterized(base, types(in, args))
|
||||
case SuperType(thistpe: Type, supertpe: Type) => warning("sbt-api: Super type (not implemented): this=" + thistpe + ", super=" + supertpe); Constants.emptyType
|
||||
case at: AnnotatedType => annotatedType(in, at)
|
||||
case rt: CompoundType => structure(rt)
|
||||
case ExistentialType(tparams, result) => new xsbti.api.Existential(processType(in, result), typeParameters(in, tparams))
|
||||
case NoType => Constants.emptyType // this can happen when there is an error that will be reported by a later phase
|
||||
case PolyType(typeParams, resultType) => new xsbti.api.Polymorphic(processType(in, resultType), typeParameters(in, typeParams))
|
||||
case Nullary(resultType) => warning("sbt-api: Unexpected nullary method type " + in + " in " + in.owner); Constants.emptyType
|
||||
case _ => warning("sbt-api: Unhandled type " + t.getClass + " : " + t); Constants.emptyType
|
||||
}
|
||||
}
|
||||
private def typeParameters(in: Symbol, s: Symbol): Array[xsbti.api.TypeParameter] = typeParameters(in, s.typeParams)
|
||||
private def typeParameters(in: Symbol, s: List[Symbol]): Array[xsbti.api.TypeParameter] = s.map(typeParameter(in,_)).toArray[xsbti.api.TypeParameter]
|
||||
private def typeParameter(in: Symbol, s: Symbol): xsbti.api.TypeParameter =
|
||||
{
|
||||
val varianceInt = s.variance
|
||||
import xsbti.api.Variance._
|
||||
val annots = annotations(in, s)
|
||||
val variance = if(varianceInt < 0) Contravariant else if(varianceInt > 0) Covariant else Invariant
|
||||
viewer(in).memberInfo(s) match
|
||||
{
|
||||
case TypeBounds(low, high) => new xsbti.api.TypeParameter( tparamID(s), annots, typeParameters(in, s), variance, processType(in, low), processType(in, high) )
|
||||
case PolyType(typeParams, base) => new xsbti.api.TypeParameter( tparamID(s), annots, typeParameters(in, typeParams), variance, processType(in, base.bounds.lo), processType(in, base.bounds.hi))
|
||||
case x => error("Unknown type parameter info: " + x.getClass)
|
||||
}
|
||||
}
|
||||
private def tparamID(s: Symbol) = s.fullName
|
||||
private def selfType(in: Symbol, s: Symbol): xsbti.api.Type = processType(in, s.thisSym.typeOfThis)
|
||||
|
||||
private def classLike(in: Symbol, c: Symbol): ClassLike = classLikeCache.getOrElseUpdate( (in,c), mkClassLike(in, c))
|
||||
private def mkClassLike(in: Symbol, c: Symbol): ClassLike =
|
||||
{
|
||||
val name = c.fullName
|
||||
val isModule = c.isModuleClass || c.isModule
|
||||
val struct = if(isModule) c.moduleClass else c
|
||||
val defType =
|
||||
if(c.isTrait) DefinitionType.Trait
|
||||
else if(isModule)
|
||||
{
|
||||
if(c.isPackage) DefinitionType.PackageModule
|
||||
else DefinitionType.Module
|
||||
}
|
||||
else DefinitionType.ClassDef
|
||||
new xsbti.api.ClassLike(defType, lzy(selfType(in, c)), lzy(structure(in, struct)), emptyStringArray, typeParameters(in, c), name, getAccess(c), getModifiers(c), annotations(in, c))
|
||||
}
|
||||
private final class TopLevelHandler(sourceFile: File) extends TopLevelTraverser
|
||||
private final class TopLevelHandler(extractApi: ExtractAPI[global.type]) extends TopLevelTraverser
|
||||
{
|
||||
val packages = new HashSet[String]
|
||||
val definitions = new ListBuffer[xsbti.api.Definition]
|
||||
def `class`(c: Symbol): Unit = definitions += classLike(c.owner, c)
|
||||
def `class`(c: Symbol): Unit = {
|
||||
definitions += extractApi.classLike(c.owner, c)
|
||||
}
|
||||
/** Record packages declared in the source file*/
|
||||
def `package`(p: Symbol)
|
||||
{
|
||||
|
|
@ -438,41 +70,7 @@ final class API(val global: CallbackGlobal) extends Compat
|
|||
}
|
||||
}
|
||||
}
|
||||
private[this] def isClass(s: Symbol) = s.isClass || s.isModule
|
||||
// necessary to ensure a stable ordering of classes in the definitions list:
|
||||
// modules and classes come first and are sorted by name
|
||||
// all other definitions come later and are not sorted
|
||||
private[this] val sortClasses = new Comparator[Symbol] {
|
||||
def compare(a: Symbol, b: Symbol) = {
|
||||
val aIsClass = isClass(a)
|
||||
val bIsClass = isClass(b)
|
||||
if(aIsClass == bIsClass)
|
||||
if(aIsClass)
|
||||
if(a.isModule == b.isModule)
|
||||
a.fullName.compareTo(b.fullName)
|
||||
else if(a.isModule)
|
||||
-1
|
||||
else
|
||||
1
|
||||
else
|
||||
0 // substantial performance hit if fullNames are compared here
|
||||
else if(aIsClass)
|
||||
-1
|
||||
else
|
||||
1
|
||||
}
|
||||
}
|
||||
private object Constants
|
||||
{
|
||||
val local = new xsbti.api.ThisQualifier
|
||||
val public = new xsbti.api.Public
|
||||
val privateLocal = new xsbti.api.Private(local)
|
||||
val protectedLocal = new xsbti.api.Protected(local)
|
||||
val unqualified = new xsbti.api.Unqualified
|
||||
val emptyPath = new xsbti.api.Path(Array())
|
||||
val thisPath = new xsbti.api.This
|
||||
val emptyType = new xsbti.api.EmptyType
|
||||
}
|
||||
|
||||
private abstract class TopLevelTraverser extends Traverser
|
||||
{
|
||||
def `class`(s: Symbol)
|
||||
|
|
@ -493,25 +91,6 @@ final class API(val global: CallbackGlobal) extends Compat
|
|||
!sym.hasFlag(Flags.SYNTHETIC) && !sym.hasFlag(Flags.JAVA)
|
||||
}
|
||||
|
||||
private def annotations(in: Symbol, s: Symbol): Array[xsbti.api.Annotation] =
|
||||
atPhase(currentRun.typerPhase) {
|
||||
val base = if(s.hasFlag(Flags.ACCESSOR)) s.accessed else NoSymbol
|
||||
val b = if(base == NoSymbol) s else base
|
||||
// annotations from bean methods are not handled because:
|
||||
// a) they are recorded as normal source methods anyway
|
||||
// b) there is no way to distinguish them from user-defined methods
|
||||
val associated = List(b, b.getter(b.enclClass), b.setter(b.enclClass)).filter(_ != NoSymbol)
|
||||
associated.flatMap( ss => annotations(in, ss.annotations) ).distinct.toArray ;
|
||||
}
|
||||
private def annotatedType(in: Symbol, at: AnnotatedType): xsbti.api.Type =
|
||||
{
|
||||
val annots = at.annotations
|
||||
if(annots.isEmpty) processType(in, at.underlying) else annotated(in, annots, at.underlying)
|
||||
}
|
||||
private def simpleName(s: Symbol): String =
|
||||
{
|
||||
val n = s.originalName
|
||||
val n2 = if(n.toString == "<init>") n else n.decode
|
||||
n2.toString.trim
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,450 @@
|
|||
package xsbt
|
||||
|
||||
import java.io.File
|
||||
import java.util.{Arrays,Comparator}
|
||||
import scala.tools.nsc.{io, plugins, symtab, Global, Phase}
|
||||
import io.{AbstractFile, PlainFile, ZipArchive}
|
||||
import plugins.{Plugin, PluginComponent}
|
||||
import symtab.Flags
|
||||
import scala.collection.mutable.{HashMap, HashSet, ListBuffer}
|
||||
import xsbti.api.{ClassLike, DefinitionType, PathComponent, SimpleType}
|
||||
|
||||
/**
|
||||
* Extracts API representation out of Symbols and Types.
|
||||
*
|
||||
* Each compilation unit should be processed by a fresh instance of this class.
|
||||
*
|
||||
* This class depends on instance of CallbackGlobal instead of regular Global because
|
||||
* it has a call to `addInheritedDependencies` method defined in CallbackGlobal. In the future
|
||||
* we should refactor this code so inherited dependencies are just accumulated in a buffer and
|
||||
* exposed to a client that can pass them to an instance of CallbackGlobal it holds.
|
||||
*/
|
||||
class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType,
|
||||
// Tracks the source file associated with the CompilationUnit currently being processed by the API phase.
|
||||
// This is used when recording inheritance dependencies.
|
||||
sourceFile: File) extends Compat {
|
||||
|
||||
import global._
|
||||
|
||||
private def error(msg: String) = throw new RuntimeException(msg)
|
||||
|
||||
// this cache reduces duplicate work both here and when persisting
|
||||
// caches on other structures had minimal effect on time and cache size
|
||||
// (tried: Definition, Modifier, Path, Id, String)
|
||||
private[this] val typeCache = new HashMap[(Symbol,Type), xsbti.api.Type]
|
||||
// these caches are necessary for correctness
|
||||
private[this] val structureCache = new HashMap[Symbol, xsbti.api.Structure]
|
||||
private[this] val classLikeCache = new HashMap[(Symbol,Symbol), xsbti.api.ClassLike]
|
||||
private[this] val pending = new HashSet[xsbti.api.Lazy[_]]
|
||||
|
||||
private[this] val emptyStringArray = new Array[String](0)
|
||||
|
||||
// call back to the xsbti.SafeLazy class in main sbt code to construct a SafeLazy instance
|
||||
// we pass a thunk, whose class is loaded by the interface class loader (this class's loader)
|
||||
// SafeLazy ensures that once the value is forced, the thunk is nulled out and so
|
||||
// references to the thunk's classes are not retained. Specifically, it allows the interface classes
|
||||
// (those in this subproject) to be garbage collected after compilation.
|
||||
private[this] val safeLazy = Class.forName("xsbti.SafeLazy").getMethod("apply", classOf[xsbti.F0[_]])
|
||||
private def lzy[S <: AnyRef](s: => S): xsbti.api.Lazy[S] =
|
||||
{
|
||||
val z = safeLazy.invoke(null, Message(s)).asInstanceOf[xsbti.api.Lazy[S]]
|
||||
pending += z
|
||||
z
|
||||
}
|
||||
|
||||
/**
|
||||
* Force all lazy structures. This is necessary so that we see the symbols/types at this phase and
|
||||
* so that we don't hold on to compiler objects and classes
|
||||
*/
|
||||
def forceStructures(): Unit =
|
||||
if(pending.isEmpty)
|
||||
structureCache.clear()
|
||||
else
|
||||
{
|
||||
val toProcess = pending.toList
|
||||
pending.clear()
|
||||
toProcess foreach { _.get() }
|
||||
forceStructures()
|
||||
}
|
||||
|
||||
private def thisPath(sym: Symbol) = path(pathComponents(sym, Constants.thisPath :: Nil))
|
||||
private def path(components: List[PathComponent]) = new xsbti.api.Path(components.toArray[PathComponent])
|
||||
private def pathComponents(sym: Symbol, postfix: List[PathComponent]): List[PathComponent] =
|
||||
{
|
||||
if(sym == NoSymbol || sym.isRoot || sym.isEmptyPackageClass || sym.isRootPackage) postfix
|
||||
else pathComponents(sym.owner, new xsbti.api.Id(simpleName(sym)) :: postfix)
|
||||
}
|
||||
private def simpleType(in: Symbol, t: Type): SimpleType =
|
||||
processType(in, t) match
|
||||
{
|
||||
case s: SimpleType => s
|
||||
case x => warning("Not a simple type:\n\tType: " + t + " (" + t.getClass + ")\n\tTransformed: " + x.getClass); Constants.emptyType
|
||||
}
|
||||
private def types(in: Symbol, t: List[Type]): Array[xsbti.api.Type] = t.toArray[Type].map(processType(in, _))
|
||||
private def projectionType(in: Symbol, pre: Type, sym: Symbol) =
|
||||
{
|
||||
if(pre == NoPrefix)
|
||||
{
|
||||
if(sym.isLocalClass || sym.isRoot || sym.isRootPackage) Constants.emptyType
|
||||
else if(sym.isTypeParameterOrSkolem || sym.isExistentiallyBound) reference(sym)
|
||||
else {
|
||||
// this appears to come from an existential type in an inherited member- not sure why isExistential is false here
|
||||
/*println("Warning: Unknown prefixless type: " + sym + " in " + sym.owner + " in " + sym.enclClass)
|
||||
println("\tFlags: " + sym.flags + ", istype: " + sym.isType + ", absT: " + sym.isAbstractType + ", alias: " + sym.isAliasType + ", nonclass: " + isNonClassType(sym))*/
|
||||
reference(sym)
|
||||
}
|
||||
}
|
||||
else if(sym.isRoot || sym.isRootPackage) Constants.emptyType
|
||||
else new xsbti.api.Projection(simpleType(in, pre), simpleName(sym))
|
||||
}
|
||||
private def reference(sym: Symbol): xsbti.api.ParameterRef = new xsbti.api.ParameterRef(tparamID(sym))
|
||||
|
||||
private def annotations(in: Symbol, as: List[AnnotationInfo]): Array[xsbti.api.Annotation] = as.toArray[AnnotationInfo].map(annotation(in,_))
|
||||
private def annotation(in: Symbol, a: AnnotationInfo) =
|
||||
new xsbti.api.Annotation(processType(in, a.atp),
|
||||
if(a.assocs.isEmpty) Array(new xsbti.api.AnnotationArgument("", a.args.mkString("(", ",", ")"))) // what else to do with a Tree?
|
||||
else a.assocs.map { case (name, value) => new xsbti.api.AnnotationArgument(name.toString, value.toString) }.toArray[xsbti.api.AnnotationArgument]
|
||||
)
|
||||
private def annotated(in: Symbol, as: List[AnnotationInfo], tpe: Type) = new xsbti.api.Annotated(processType(in, tpe), annotations(in, as))
|
||||
|
||||
private def viewer(s: Symbol) = (if(s.isModule) s.moduleClass else s).thisType
|
||||
private def printMember(label: String, in: Symbol, t: Type) = println(label + " in " + in + " : " + t + " (debug: " + debugString(t) + " )")
|
||||
private def defDef(in: Symbol, s: Symbol) =
|
||||
{
|
||||
def build(t: Type, typeParams: Array[xsbti.api.TypeParameter], valueParameters: List[xsbti.api.ParameterList]): xsbti.api.Def =
|
||||
{
|
||||
def parameterList(syms: List[Symbol]): xsbti.api.ParameterList =
|
||||
{
|
||||
val isImplicitList = syms match { case head :: _ => isImplicit(head); case _ => false }
|
||||
new xsbti.api.ParameterList(syms.map(parameterS).toArray, isImplicitList)
|
||||
}
|
||||
t match
|
||||
{
|
||||
case PolyType(typeParams0, base) =>
|
||||
assert(typeParams.isEmpty)
|
||||
assert(valueParameters.isEmpty)
|
||||
build(base, typeParameters(in, typeParams0), Nil)
|
||||
case MethodType(params, resultType) =>
|
||||
build(resultType, typeParams, parameterList(params) :: valueParameters)
|
||||
case Nullary(resultType) => // 2.9 and later
|
||||
build(resultType, typeParams, valueParameters)
|
||||
case returnType =>
|
||||
val t2 = processType(in, dropConst(returnType))
|
||||
new xsbti.api.Def(valueParameters.reverse.toArray, t2, typeParams, simpleName(s), getAccess(s), getModifiers(s), annotations(in,s))
|
||||
}
|
||||
}
|
||||
def parameterS(s: Symbol): xsbti.api.MethodParameter =
|
||||
makeParameter(simpleName(s), s.info, s.info.typeSymbol, s)
|
||||
|
||||
// paramSym is only for 2.8 and is to determine if the parameter has a default
|
||||
def makeParameter(name: String, tpe: Type, ts: Symbol, paramSym: Symbol): xsbti.api.MethodParameter =
|
||||
{
|
||||
import xsbti.api.ParameterModifier._
|
||||
val (t, special) =
|
||||
if(ts == definitions.RepeatedParamClass)// || s == definitions.JavaRepeatedParamClass)
|
||||
(tpe.typeArgs(0), Repeated)
|
||||
else if(ts == definitions.ByNameParamClass)
|
||||
(tpe.typeArgs(0), ByName)
|
||||
else
|
||||
(tpe, Plain)
|
||||
new xsbti.api.MethodParameter(name, processType(in, t), hasDefault(paramSym), special)
|
||||
}
|
||||
val t = viewer(in).memberInfo(s)
|
||||
build(t, Array(), Nil)
|
||||
}
|
||||
private def hasDefault(s: Symbol) = s != NoSymbol && s.hasFlag(Flags.DEFAULTPARAM)
|
||||
private def fieldDef[T](in: Symbol, s: Symbol, keepConst: Boolean, create: (xsbti.api.Type, String, xsbti.api.Access, xsbti.api.Modifiers, Array[xsbti.api.Annotation]) => T): T =
|
||||
{
|
||||
val t = dropNullary(viewer(in).memberType(s))
|
||||
val t2 = if(keepConst) t else dropConst(t)
|
||||
create(processType(in, t2), simpleName(s), getAccess(s), getModifiers(s), annotations(in, s))
|
||||
}
|
||||
private def dropConst(t: Type): Type = t match {
|
||||
case ConstantType(constant) => constant.tpe
|
||||
case _ => t
|
||||
}
|
||||
private def dropNullary(t: Type): Type = t match {
|
||||
case Nullary(un) => un
|
||||
case _ => t
|
||||
}
|
||||
|
||||
private def typeDef(in: Symbol, s: Symbol): xsbti.api.TypeMember =
|
||||
{
|
||||
val (typeParams, tpe) =
|
||||
viewer(in).memberInfo(s) match
|
||||
{
|
||||
case PolyType(typeParams0, base) => (typeParameters(in, typeParams0), base)
|
||||
case t => (Array[xsbti.api.TypeParameter](), t)
|
||||
}
|
||||
val name = simpleName(s)
|
||||
val access = getAccess(s)
|
||||
val modifiers = getModifiers(s)
|
||||
val as = annotations(in, s)
|
||||
|
||||
if(s.isAliasType)
|
||||
new xsbti.api.TypeAlias(processType(in, tpe), typeParams, name, access, modifiers, as)
|
||||
else if(s.isAbstractType)
|
||||
{
|
||||
val bounds = tpe.bounds
|
||||
new xsbti.api.TypeDeclaration(processType(in, bounds.lo), processType(in, bounds.hi), typeParams, name, access, modifiers, as)
|
||||
}
|
||||
else
|
||||
error("Unknown type member" + s)
|
||||
}
|
||||
|
||||
private def structure(in: Symbol, s: Symbol): xsbti.api.Structure = structure(viewer(in).memberInfo(s), s, true)
|
||||
private def structure(info: Type): xsbti.api.Structure = structure(info, info.typeSymbol, false)
|
||||
private def structure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure =
|
||||
structureCache.getOrElseUpdate( s, mkStructure(info, s, inherit))
|
||||
|
||||
private def removeConstructors(ds: List[Symbol]): List[Symbol] = ds filter { !_.isConstructor}
|
||||
|
||||
private def mkStructure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure =
|
||||
{
|
||||
val (declared, inherited) = info.members.reverse.partition(_.owner == s)
|
||||
val baseTypes = info.baseClasses.tail.map(info.baseType)
|
||||
val ds = if(s.isModuleClass) removeConstructors(declared) else declared
|
||||
val is = if(inherit) removeConstructors(inherited) else Nil
|
||||
mkStructure(s, baseTypes, ds, is)
|
||||
}
|
||||
|
||||
// If true, this template is publicly visible and should be processed as a public inheritance dependency.
|
||||
// Local classes and local refinements will never be traversed by the api phase, so we don't need to check for that.
|
||||
private[this] def isPublicStructure(s: Symbol): Boolean =
|
||||
s.isStructuralRefinement ||
|
||||
// do not consider templates that are private[this] or private
|
||||
!(s.isPrivate && (s.privateWithin == NoSymbol || s.isLocal))
|
||||
|
||||
private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure = {
|
||||
if(isPublicStructure(s))
|
||||
addInheritedDependencies(sourceFile, bases.map(_.dealias.typeSymbol))
|
||||
new xsbti.api.Structure(lzy(types(s, bases)), lzy(processDefinitions(s, declared)), lzy(processDefinitions(s, inherited)))
|
||||
}
|
||||
private def processDefinitions(in: Symbol, defs: List[Symbol]): Array[xsbti.api.Definition] =
|
||||
sort(defs.toArray).flatMap( (d: Symbol) => definition(in, d))
|
||||
private[this] def sort(defs: Array[Symbol]): Array[Symbol] = {
|
||||
Arrays.sort(defs, sortClasses)
|
||||
defs
|
||||
}
|
||||
|
||||
private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.Definition] =
|
||||
{
|
||||
def mkVar = Some(fieldDef(in, sym, false, new xsbti.api.Var(_,_,_,_,_)))
|
||||
def mkVal = Some(fieldDef(in, sym, true, new xsbti.api.Val(_,_,_,_,_)))
|
||||
if(isClass(sym))
|
||||
if(ignoreClass(sym)) None else Some(classLike(in, sym))
|
||||
else if(sym.isNonClassType)
|
||||
Some(typeDef(in, sym))
|
||||
else if(sym.isVariable)
|
||||
if(isSourceField(sym)) mkVar else None
|
||||
else if(sym.isStable)
|
||||
if(isSourceField(sym)) mkVal else None
|
||||
else if(sym.isSourceMethod && !sym.isSetter)
|
||||
if(sym.isGetter) mkVar else Some(defDef(in, sym))
|
||||
else
|
||||
None
|
||||
}
|
||||
private def ignoreClass(sym: Symbol): Boolean =
|
||||
sym.isLocalClass || sym.isAnonymousClass || sym.fullName.endsWith(LocalChild.toString)
|
||||
|
||||
// This filters private[this] vals/vars that were not in the original source.
|
||||
// The getter will be used for processing instead.
|
||||
private def isSourceField(sym: Symbol): Boolean =
|
||||
{
|
||||
val getter = sym.getter(sym.enclClass)
|
||||
// the check `getter eq sym` is a precaution against infinite recursion
|
||||
// `isParamAccessor` does not exist in all supported versions of Scala, so the flag check is done directly
|
||||
(getter == NoSymbol && !sym.hasFlag(Flags.PARAMACCESSOR)) || (getter eq sym)
|
||||
}
|
||||
private def getModifiers(s: Symbol): xsbti.api.Modifiers =
|
||||
{
|
||||
import Flags._
|
||||
val absOver = s.hasFlag(ABSOVERRIDE)
|
||||
val abs = s.hasFlag(ABSTRACT) || s.hasFlag(DEFERRED) || absOver
|
||||
val over = s.hasFlag(OVERRIDE) || absOver
|
||||
new xsbti.api.Modifiers(abs, over, s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY), hasMacro(s))
|
||||
}
|
||||
|
||||
private def isImplicit(s: Symbol) = s.hasFlag(Flags.IMPLICIT)
|
||||
private def getAccess(c: Symbol): xsbti.api.Access =
|
||||
{
|
||||
if(c.isPublic) Constants.public
|
||||
else if(c.isPrivateLocal) Constants.privateLocal
|
||||
else if(c.isProtectedLocal) Constants.protectedLocal
|
||||
else
|
||||
{
|
||||
val within = c.privateWithin
|
||||
val qualifier = if(within == NoSymbol) Constants.unqualified else new xsbti.api.IdQualifier(within.fullName)
|
||||
if(c.hasFlag(Flags.PROTECTED)) new xsbti.api.Protected(qualifier)
|
||||
else new xsbti.api.Private(qualifier)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all types that directly refer to the `forbidden` symbol by `NoType`.
|
||||
* (a specialized version of substThisAndSym)
|
||||
*/
|
||||
class SuppressSymbolRef(forbidden: Symbol) extends TypeMap {
|
||||
def apply(tp: Type) =
|
||||
if (tp.typeSymbolDirect == forbidden) NoType
|
||||
else mapOver(tp)
|
||||
}
|
||||
|
||||
private def processType(in: Symbol, t: Type): xsbti.api.Type = typeCache.getOrElseUpdate((in, t), makeType(in, t))
|
||||
private def makeType(in: Symbol, t: Type): xsbti.api.Type =
|
||||
{
|
||||
|
||||
val dealiased = t match {
|
||||
case TypeRef(_, sym, _) if sym.isAliasType => t.dealias
|
||||
case _ => t
|
||||
}
|
||||
|
||||
dealiased match
|
||||
{
|
||||
case NoPrefix => Constants.emptyType
|
||||
case ThisType(sym) => new xsbti.api.Singleton(thisPath(sym))
|
||||
case SingleType(pre, sym) => projectionType(in, pre, sym)
|
||||
case ConstantType(constant) => new xsbti.api.Constant(processType(in, constant.tpe), constant.stringValue)
|
||||
|
||||
/* explaining the special-casing of references to refinement classes (https://support.typesafe.com/tickets/1882)
|
||||
*
|
||||
* goal: a representation of type references to refinement classes that's stable across compilation runs
|
||||
* (and thus insensitive to typing from source or unpickling from bytecode)
|
||||
*
|
||||
* problem: the current representation, which corresponds to the owner chain of the refinement:
|
||||
* 1. is affected by pickling, so typing from source or using unpickled symbols give different results (because the unpickler "localizes" owners -- this could be fixed in the compiler)
|
||||
* 2. can't distinguish multiple refinements in the same owner (this is a limitation of SBT's internal representation and cannot be fixed in the compiler)
|
||||
*
|
||||
* potential solutions:
|
||||
* - simply drop the reference: won't work as collapsing all refinement types will cause recompilation to be skipped when a refinement is changed to another refinement
|
||||
* - represent the symbol in the api: can't think of a stable way of referring to an anonymous symbol whose owner changes when pickled
|
||||
* + expand the reference to the corresponding refinement type: doing that recursively may not terminate, but we can deal with that by approximating recursive references
|
||||
* (all we care about is being sound for recompilation: recompile iff a dependency changes, and this will happen as long as we have one unrolling of the reference to the refinement)
|
||||
*/
|
||||
case TypeRef(pre, sym, Nil) if sym.isRefinementClass =>
|
||||
// Since we only care about detecting changes reliably, we unroll a reference to a refinement class once.
|
||||
// Recursive references are simply replaced by NoType -- changes to the type will be seen in the first unrolling.
|
||||
// The API need not be type correct, so this truncation is acceptable. Most of all, the API should be compact.
|
||||
val unrolling = pre.memberInfo(sym) // this is a refinement type
|
||||
|
||||
// in case there are recursive references, suppress them -- does this ever happen?
|
||||
// we don't have a test case for this, so warn and hope we'll get a contribution for it :-)
|
||||
val withoutRecursiveRefs = new SuppressSymbolRef(sym).mapOver(unrolling)
|
||||
if (unrolling ne withoutRecursiveRefs)
|
||||
reporter.warning(sym.pos, "sbt-api: approximated refinement ref"+ t +" (== "+ unrolling +") to "+ withoutRecursiveRefs +"\nThis is currently untested, please report the code you were compiling.")
|
||||
|
||||
structure(withoutRecursiveRefs)
|
||||
case tr @ TypeRef(pre, sym, args) =>
|
||||
val base = projectionType(in, pre, sym)
|
||||
if(args.isEmpty)
|
||||
if(isRawType(tr))
|
||||
processType(in, rawToExistential(tr))
|
||||
else
|
||||
base
|
||||
else
|
||||
new xsbti.api.Parameterized(base, types(in, args))
|
||||
case SuperType(thistpe: Type, supertpe: Type) => warning("sbt-api: Super type (not implemented): this=" + thistpe + ", super=" + supertpe); Constants.emptyType
|
||||
case at: AnnotatedType => annotatedType(in, at)
|
||||
case rt: CompoundType => structure(rt)
|
||||
case ExistentialType(tparams, result) => new xsbti.api.Existential(processType(in, result), typeParameters(in, tparams))
|
||||
case NoType => Constants.emptyType // this can happen when there is an error that will be reported by a later phase
|
||||
case PolyType(typeParams, resultType) => new xsbti.api.Polymorphic(processType(in, resultType), typeParameters(in, typeParams))
|
||||
case Nullary(resultType) => warning("sbt-api: Unexpected nullary method type " + in + " in " + in.owner); Constants.emptyType
|
||||
case _ => warning("sbt-api: Unhandled type " + t.getClass + " : " + t); Constants.emptyType
|
||||
}
|
||||
}
|
||||
private def typeParameters(in: Symbol, s: Symbol): Array[xsbti.api.TypeParameter] = typeParameters(in, s.typeParams)
|
||||
private def typeParameters(in: Symbol, s: List[Symbol]): Array[xsbti.api.TypeParameter] = s.map(typeParameter(in,_)).toArray[xsbti.api.TypeParameter]
|
||||
private def typeParameter(in: Symbol, s: Symbol): xsbti.api.TypeParameter =
|
||||
{
|
||||
val varianceInt = s.variance
|
||||
import xsbti.api.Variance._
|
||||
val annots = annotations(in, s)
|
||||
val variance = if(varianceInt < 0) Contravariant else if(varianceInt > 0) Covariant else Invariant
|
||||
viewer(in).memberInfo(s) match
|
||||
{
|
||||
case TypeBounds(low, high) => new xsbti.api.TypeParameter( tparamID(s), annots, typeParameters(in, s), variance, processType(in, low), processType(in, high) )
|
||||
case PolyType(typeParams, base) => new xsbti.api.TypeParameter( tparamID(s), annots, typeParameters(in, typeParams), variance, processType(in, base.bounds.lo), processType(in, base.bounds.hi))
|
||||
case x => error("Unknown type parameter info: " + x.getClass)
|
||||
}
|
||||
}
|
||||
private def tparamID(s: Symbol) = s.fullName
|
||||
private def selfType(in: Symbol, s: Symbol): xsbti.api.Type = processType(in, s.thisSym.typeOfThis)
|
||||
|
||||
def classLike(in: Symbol, c: Symbol): ClassLike = classLikeCache.getOrElseUpdate( (in,c), mkClassLike(in, c))
|
||||
private def mkClassLike(in: Symbol, c: Symbol): ClassLike =
|
||||
{
|
||||
val name = c.fullName
|
||||
val isModule = c.isModuleClass || c.isModule
|
||||
val struct = if(isModule) c.moduleClass else c
|
||||
val defType =
|
||||
if(c.isTrait) DefinitionType.Trait
|
||||
else if(isModule)
|
||||
{
|
||||
if(c.isPackage) DefinitionType.PackageModule
|
||||
else DefinitionType.Module
|
||||
}
|
||||
else DefinitionType.ClassDef
|
||||
new xsbti.api.ClassLike(defType, lzy(selfType(in, c)), lzy(structure(in, struct)), emptyStringArray, typeParameters(in, c), name, getAccess(c), getModifiers(c), annotations(in, c))
|
||||
}
|
||||
|
||||
private[this] def isClass(s: Symbol) = s.isClass || s.isModule
|
||||
// necessary to ensure a stable ordering of classes in the definitions list:
|
||||
// modules and classes come first and are sorted by name
|
||||
// all other definitions come later and are not sorted
|
||||
private[this] val sortClasses = new Comparator[Symbol] {
|
||||
def compare(a: Symbol, b: Symbol) = {
|
||||
val aIsClass = isClass(a)
|
||||
val bIsClass = isClass(b)
|
||||
if(aIsClass == bIsClass)
|
||||
if(aIsClass)
|
||||
if(a.isModule == b.isModule)
|
||||
a.fullName.compareTo(b.fullName)
|
||||
else if(a.isModule)
|
||||
-1
|
||||
else
|
||||
1
|
||||
else
|
||||
0 // substantial performance hit if fullNames are compared here
|
||||
else if(aIsClass)
|
||||
-1
|
||||
else
|
||||
1
|
||||
}
|
||||
}
|
||||
private object Constants
|
||||
{
|
||||
val local = new xsbti.api.ThisQualifier
|
||||
val public = new xsbti.api.Public
|
||||
val privateLocal = new xsbti.api.Private(local)
|
||||
val protectedLocal = new xsbti.api.Protected(local)
|
||||
val unqualified = new xsbti.api.Unqualified
|
||||
val emptyPath = new xsbti.api.Path(Array())
|
||||
val thisPath = new xsbti.api.This
|
||||
val emptyType = new xsbti.api.EmptyType
|
||||
}
|
||||
|
||||
private def simpleName(s: Symbol): String =
|
||||
{
|
||||
val n = s.originalName
|
||||
val n2 = if(n.toString == "<init>") n else n.decode
|
||||
n2.toString.trim
|
||||
}
|
||||
|
||||
private def annotations(in: Symbol, s: Symbol): Array[xsbti.api.Annotation] =
|
||||
atPhase(currentRun.typerPhase) {
|
||||
val base = if(s.hasFlag(Flags.ACCESSOR)) s.accessed else NoSymbol
|
||||
val b = if(base == NoSymbol) s else base
|
||||
// annotations from bean methods are not handled because:
|
||||
// a) they are recorded as normal source methods anyway
|
||||
// b) there is no way to distinguish them from user-defined methods
|
||||
val associated = List(b, b.getter(b.enclClass), b.setter(b.enclClass)).filter(_ != NoSymbol)
|
||||
associated.flatMap( ss => annotations(in, ss.annotations) ).distinct.toArray ;
|
||||
}
|
||||
private def annotatedType(in: Symbol, at: AnnotatedType): xsbti.api.Type =
|
||||
{
|
||||
val annots = at.annotations
|
||||
if(annots.isEmpty) processType(in, at.underlying) else annotated(in, annots, at.underlying)
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue