2009-11-16 14:46:47 +01:00
/* sbt -- Simple Build Tool
2011-03-11 22:57:15 +01:00
* Copyright 2008 , 2009 , 2010 , 2011 Mark Harrah
2009-11-16 14:46:47 +01:00
*/
package xsbt
import java.io.File
2012-05-01 02:34:48 +02:00
import java.util. { Arrays , Comparator }
2009-11-16 14:46:47 +01:00
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 }
2010-06-16 02:38:18 +02:00
import xsbti.api. { ClassLike , DefinitionType , PathComponent , SimpleType }
2009-11-16 14:46:47 +01:00
object API
{
val name = "xsbt-api"
}
2011-02-09 02:30:15 +01:00
2012-04-29 00:58:38 +02:00
final class API ( val global : CallbackGlobal ) extends Compat
2009-11-16 14:46:47 +01:00
{
import global._
def error ( msg : String ) = throw new RuntimeException ( msg )
2012-11-20 03:36:10 +01:00
@inline def debug ( msg : => String ) = if ( settings . verbose . value ) inform ( msg )
2009-11-16 14:46:47 +01:00
def newPhase ( prev : Phase ) = new ApiPhase ( prev )
class ApiPhase ( prev : Phase ) extends Phase ( prev )
{
override def description = "Extracts the public API from source files."
def name = API . name
2009-11-23 04:54:17 +01:00
def run : Unit =
{
2010-07-02 12:57:03 +02:00
val start = System . currentTimeMillis
currentRun . units . foreach ( processUnit )
val stop = System . currentTimeMillis
2011-05-30 01:17:31 +02:00
debug ( "API phase took : " + ( ( stop - start ) / 1000.0 ) + " s" )
2009-11-23 04:54:17 +01:00
}
2011-04-27 03:19:56 +02:00
def processUnit ( unit : CompilationUnit ) = if ( ! unit . isJava ) processScalaUnit ( unit )
def processScalaUnit ( unit : CompilationUnit )
2009-11-16 14:46:47 +01:00
{
val sourceFile = unit . source . file . file
2011-05-30 01:17:31 +02:00
debug ( "Traversing " + sourceFile )
2009-11-16 14:46:47 +01:00
val traverser = new TopLevelHandler ( sourceFile )
traverser . apply ( unit . body )
val packages = traverser . packages . toArray [ String ] . map ( p => new xsbti . api . Package ( p ) )
2011-06-01 08:19:46 +02:00
val source = new xsbti . api . SourceAPI ( packages , traverser . definitions . toArray [ xsbti . api . Definition ] )
2010-10-23 03:55:16 +02:00
forceStructures ( )
2010-10-30 23:46:56 +02:00
clearCaches ( )
2009-11-16 14:46:47 +01:00
callback . api ( sourceFile , source )
2010-06-16 02:38:18 +02:00
}
2009-11-16 14:46:47 +01:00
}
2010-10-23 03:55:16 +02:00
2010-10-30 23:46:56 +02:00
// 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)
2010-11-10 02:49:23 +01:00
private [ this ] val typeCache = new HashMap [ ( Symbol ,Type ) , xsbti . api . Type ]
2010-10-30 23:46:56 +02:00
// these caches are necessary for correctness
2010-10-23 03:55:16 +02:00
private [ this ] val structureCache = new HashMap [ Symbol , xsbti . api . Structure ]
2010-11-10 02:49:23 +01:00
private [ this ] val classLikeCache = new HashMap [ ( Symbol ,Symbol ) , xsbti . api . ClassLike ]
2010-10-23 03:55:16 +02:00
private [ this ] val pending = new HashSet [ xsbti . api . Lazy [ _ ] ]
2011-10-20 04:23:47 +02:00
private [ this ] val emptyStringArray = new Array [ String ] ( 0 )
2010-10-30 23:46:56 +02:00
// 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 ( )
}
2010-10-23 03:55:16 +02:00
// 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
2011-03-11 22:57:15 +01:00
// (those in this subproject) to be garbage collected after compilation.
2010-10-23 03:55:16 +02:00
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 ( )
}
2010-06-16 02:38:18 +02:00
private def thisPath ( sym : Symbol ) = path ( pathComponents ( sym , Constants . thisPath : : Nil ) )
2009-11-16 14:46:47 +01:00
private def path ( components : List [ PathComponent ] ) = new xsbti . api . Path ( components . toArray [ PathComponent ] )
private def pathComponents ( sym : Symbol , postfix : List [ PathComponent ] ) : List [ PathComponent ] =
{
2010-07-02 12:57:03 +02:00
if ( sym == NoSymbol || sym . isRoot || sym . isEmptyPackageClass || sym . isRootPackage ) postfix
2010-01-08 03:41:20 +01:00
else pathComponents ( sym . owner , new xsbti . api . Id ( simpleName ( sym ) ) : : postfix )
2009-11-16 14:46:47 +01:00
}
2010-11-10 02:49:23 +01:00
private def simpleType ( in : Symbol , t : Type ) : SimpleType =
processType ( in , t ) match
2009-11-16 14:46:47 +01:00
{
case s : SimpleType => s
2012-01-04 16:20:22 +01:00
case x => warning ( "Not a simple type:\n\tType: " + t + " (" + t . getClass + ")\n\tTransformed: " + x . getClass ) ; Constants . emptyType
2009-11-16 14:46:47 +01:00
}
2010-11-10 02:49:23 +01:00
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 ) =
2009-11-16 14:46:47 +01:00
{
2010-01-08 03:41:20 +01:00
if ( pre == NoPrefix )
{
2010-10-23 03:55:16 +02:00
if ( sym . isLocalClass || sym . isRoot || sym . isRootPackage ) Constants . emptyType
2011-08-18 03:50:46 +02:00
else if ( sym . isTypeParameterOrSkolem || sym . isExistentiallyBound ) reference ( sym )
2010-10-23 03:55:16 +02:00
else {
2010-11-10 02:49:23 +01:00
// 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 ) ) */
2010-10-23 03:55:16 +02:00
reference ( sym )
}
2010-01-08 03:41:20 +01:00
}
2009-11-16 14:46:47 +01:00
else if ( sym . isRoot || sym . isRootPackage ) Constants . emptyType
2012-10-15 18:42:27 +02:00
else new xsbti . api . Projection ( simpleType ( in , pre ) , simpleName ( sym ) )
2009-11-16 14:46:47 +01:00
}
2012-05-13 05:12:29 +02:00
private def reference ( sym : Symbol ) : xsbti . api . ParameterRef = new xsbti . api . ParameterRef ( tparamID ( sym ) )
2010-10-23 03:55:16 +02:00
2010-11-10 02:49:23 +01:00
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 ) =
2011-05-24 00:40:03 +02:00
new xsbti . api . Annotation ( processType ( in , a . atp ) ,
2010-01-24 06:11:43 +01:00
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 ]
)
2012-10-05 15:06:26 +02:00
private def annotated ( in : Symbol , as : List [ AnnotationInfo ] , tpe : Type ) = new xsbti . api . Annotated ( processType ( in , tpe ) , annotations ( in , as ) )
2009-11-16 14:46:47 +01:00
2010-10-23 03:55:16 +02:00
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 ) =
2009-11-16 14:46:47 +01:00
{
def build ( t : Type , typeParams : Array [ xsbti . api . TypeParameter ] , valueParameters : List [ xsbti . api . ParameterList ] ) : xsbti . api . Def =
{
2011-08-18 03:50:46 +02:00
def parameterList ( syms : List [ Symbol ] ) : xsbti . api . ParameterList =
2009-11-16 14:46:47 +01:00
{
2009-11-25 05:01:05 +01:00
val isImplicitList = syms match { case head : : _ => isImplicit ( head ) ; case _ => false }
2009-11-16 14:46:47 +01:00
new xsbti . api . ParameterList ( syms . map ( parameterS ) . toArray , isImplicitList )
}
t match
{
case PolyType ( typeParams0 , base ) =>
assert ( typeParams . isEmpty )
assert ( valueParameters . isEmpty )
2010-11-10 02:49:23 +01:00
build ( base , typeParameters ( in , typeParams0 ) , Nil )
2011-08-18 03:50:46 +02:00
case MethodType ( params , resultType ) =>
build ( resultType , typeParams , parameterList ( params ) : : valueParameters )
2011-02-09 02:30:15 +01:00
case Nullary ( resultType ) => // 2.9 and later
build ( resultType , typeParams , valueParameters )
2009-11-16 14:46:47 +01:00
case returnType =>
2011-02-15 00:59:54 +01:00
val t2 = processType ( in , dropConst ( returnType ) )
new xsbti . api . Def ( valueParameters . reverse . toArray , t2 , typeParams , simpleName ( s ) , getAccess ( s ) , getModifiers ( s ) , annotations ( in , s ) )
2009-11-16 14:46:47 +01:00
}
}
2010-10-23 03:55:16 +02:00
def parameterS ( s : Symbol ) : xsbti . api . MethodParameter =
2012-10-15 18:42:27 +02:00
makeParameter ( simpleName ( s ) , s . info , s . info . typeSymbol , s )
2010-10-23 03:55:16 +02:00
2010-09-28 00:48:12 +02:00
// 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 =
2009-11-16 14:46:47 +01:00
{
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 )
2010-11-10 02:49:23 +01:00
new xsbti . api . MethodParameter ( name , processType ( in , t ) , hasDefault ( paramSym ) , special )
2009-11-16 14:46:47 +01:00
}
2010-10-23 03:55:16 +02:00
val t = viewer ( in ) . memberInfo ( s )
build ( t , Array ( ) , Nil )
2009-11-16 14:46:47 +01:00
}
2011-08-18 03:50:46 +02:00
private def hasDefault ( s : Symbol ) = s != NoSymbol && s . hasFlag ( Flags . DEFAULTPARAM )
2011-02-15 00:59:54 +01:00
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 =
2010-10-23 03:55:16 +02:00
{
2011-02-09 02:30:15 +01:00
val t = dropNullary ( viewer ( in ) . memberType ( s ) )
2011-02-15 00:59:54 +01:00
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
2010-10-23 03:55:16 +02:00
}
2011-02-09 02:30:15 +01:00
private def dropNullary ( t : Type ) : Type = t match {
case Nullary ( un ) => un
case _ => t
}
2010-01-24 06:11:43 +01:00
2010-10-23 03:55:16 +02:00
private def typeDef ( in : Symbol , s : Symbol ) : xsbti . api . TypeMember =
2009-11-23 04:54:17 +01:00
{
val ( typeParams , tpe ) =
2010-10-23 03:55:16 +02:00
viewer ( in ) . memberInfo ( s ) match
2009-11-23 04:54:17 +01:00
{
2010-11-10 02:49:23 +01:00
case PolyType ( typeParams0 , base ) => ( typeParameters ( in , typeParams0 ) , base )
2009-11-23 04:54:17 +01:00
case t => ( Array [ xsbti . api . TypeParameter ] ( ) , t )
}
2010-01-08 03:41:20 +01:00
val name = simpleName ( s )
2009-11-23 04:54:17 +01:00
val access = getAccess ( s )
val modifiers = getModifiers ( s )
2010-11-10 02:49:23 +01:00
val as = annotations ( in , s )
2009-11-23 04:54:17 +01:00
if ( s . isAliasType )
2010-11-10 02:49:23 +01:00
new xsbti . api . TypeAlias ( processType ( in , tpe ) , typeParams , name , access , modifiers , as )
2009-11-23 04:54:17 +01:00
else if ( s . isAbstractType )
{
val bounds = tpe . bounds
2010-11-10 02:49:23 +01:00
new xsbti . api . TypeDeclaration ( processType ( in , bounds . lo ) , processType ( in , bounds . hi ) , typeParams , name , access , modifiers , as )
2009-11-23 04:54:17 +01:00
}
else
error ( "Unknown type member" + s )
}
2009-11-16 14:46:47 +01:00
2010-11-10 02:49:23 +01:00
private def structure ( in : Symbol , s : Symbol ) : xsbti . api . Structure = structure ( viewer ( in ) . memberInfo ( s ) , s , true )
2010-10-23 03:55:16 +02:00
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 ) )
2010-11-10 02:49:23 +01:00
private def removeConstructors ( ds : List [ Symbol ] ) : List [ Symbol ] = ds filter { ! _ . isConstructor }
2010-10-23 03:55:16 +02:00
private def mkStructure ( info : Type , s : Symbol , inherit : Boolean ) : xsbti . api . Structure =
2009-11-23 04:54:17 +01:00
{
2010-09-18 03:30:47 +02:00
val ( declared , inherited ) = info . members . reverse . partition ( _ . owner == s )
2010-10-23 03:55:16 +02:00
val baseTypes = info . baseClasses . tail . map ( info . baseType )
2010-11-10 02:49:23 +01:00
val ds = if ( s . isModuleClass ) removeConstructors ( declared ) else declared
val is = if ( inherit ) removeConstructors ( inherited ) else Nil
mkStructure ( s , baseTypes , ds , is )
2009-11-23 04:54:17 +01:00
}
2010-10-23 03:55:16 +02:00
private def mkStructure ( s : Symbol , bases : List [ Type ] , declared : List [ Symbol ] , inherited : List [ Symbol ] ) : xsbti . api . Structure =
2010-11-10 02:49:23 +01:00
new xsbti . api . Structure ( lzy ( types ( s , bases ) ) , lzy ( processDefinitions ( s , declared ) ) , lzy ( processDefinitions ( s , inherited ) ) )
2010-10-23 03:55:16 +02:00
private def processDefinitions ( in : Symbol , defs : List [ Symbol ] ) : Array [ xsbti . api . Definition ] =
2012-05-01 02:34:48 +02:00
sort ( defs . toArray ) . flatMap ( ( d : Symbol ) => definition ( in , d ) )
private [ this ] def sort ( defs : Array [ Symbol ] ) : Array [ Symbol ] = {
Arrays . sort ( defs , sortClasses )
defs
}
2010-10-23 03:55:16 +02:00
private def definition ( in : Symbol , sym : Symbol ) : Option [ xsbti . api . Definition ] =
2009-11-16 14:46:47 +01:00
{
2011-02-15 00:59:54 +01:00
def mkVar = Some ( fieldDef ( in , sym , false , new xsbti . api . Var ( _ , _ , _ , _ , _ ) ) )
def mkVal = Some ( fieldDef ( in , sym , true , new xsbti . api . Val ( _ , _ , _ , _ , _ ) ) )
2012-05-01 02:34:48 +02:00
if ( isClass ( sym ) )
2010-11-10 02:49:23 +01:00
if ( ignoreClass ( sym ) ) None else Some ( classLike ( in , sym ) )
2011-08-18 03:50:46 +02:00
else if ( sym . isNonClassType )
2010-10-23 03:55:16 +02:00
Some ( typeDef ( in , sym ) )
2010-09-18 04:14:48 +02:00
else if ( sym . isVariable )
2010-10-23 03:55:16 +02:00
if ( isSourceField ( sym ) ) mkVar else None
2010-09-18 04:14:48 +02:00
else if ( sym . isStable )
2010-10-23 03:55:16 +02:00
if ( isSourceField ( sym ) ) mkVal else None
2010-09-18 04:14:48 +02:00
else if ( sym . isSourceMethod && ! sym . isSetter )
2010-10-23 03:55:16 +02:00
if ( sym . isGetter ) mkVar else Some ( defDef ( in , sym ) )
else
None
2010-09-18 04:14:48 +02:00
}
2010-11-10 02:49:23 +01:00
private def ignoreClass ( sym : Symbol ) : Boolean =
2012-02-05 03:10:30 +01:00
sym . isLocalClass || sym . isAnonymousClass || sym . fullName . endsWith ( LocalChild . toString )
2010-11-10 02:49:23 +01:00
2010-09-18 04:14:48 +02:00
// 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 )
2009-11-16 14:46:47 +01:00
}
private def getModifiers ( s : Symbol ) : xsbti . api . Modifiers =
{
2012-03-04 19:07:33 +01:00
import Flags._
2010-10-23 03:55:16 +02:00
new xsbti . api . Modifiers ( s . hasFlag ( ABSTRACT ) || s . hasFlag ( DEFERRED ) , s . hasFlag ( OVERRIDE ) ,
2012-03-04 19:07:33 +01:00
s . isFinal , s . hasFlag ( SEALED ) , isImplicit ( s ) , s . hasFlag ( LAZY ) , hasMacro ( s ) )
2009-11-16 14:46:47 +01:00
}
2010-10-30 23:46:56 +02:00
2009-11-16 14:46:47 +01:00
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
2011-08-18 03:50:46 +02:00
val qualifier = if ( within == NoSymbol ) Constants . unqualified else new xsbti . api . IdQualifier ( within . fullName )
2010-09-18 03:29:29 +02:00
if ( c . hasFlag ( Flags . PROTECTED ) ) new xsbti . api . Protected ( qualifier )
else new xsbti . api . Private ( qualifier )
2009-11-16 14:46:47 +01:00
}
}
2012-11-20 03:36:10 +01:00
/* *
* 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 )
}
2010-11-10 02:49:23 +01:00
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 =
2009-11-16 14:46:47 +01:00
{
2010-10-23 03:55:16 +02:00
def dealias ( t : Type ) = t match { case TypeRef ( _ , sym , _ ) if sym . isAliasType => t . normalize ; case _ => t }
dealias ( t ) match
2009-11-16 14:46:47 +01:00
{
case NoPrefix => Constants . emptyType
case ThisType ( sym ) => new xsbti . api . Singleton ( thisPath ( sym ) )
2010-11-10 02:49:23 +01:00
case SingleType ( pre , sym ) => projectionType ( in , pre , sym )
2011-02-15 00:59:54 +01:00
case ConstantType ( constant ) => new xsbti . api . Constant ( processType ( in , constant . tpe ) , constant . stringValue )
2012-11-20 03:36:10 +01:00
/* 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 type s 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 )
2012-05-13 05:12:29 +02:00
case tr @ TypeRef ( pre , sym , args ) =>
2010-11-10 02:49:23 +01:00
val base = projectionType ( in , pre , sym )
2012-05-13 05:12:29 +02:00
if ( args . isEmpty )
if ( isRaw ( sym , args ) )
processType ( in , rawToExistential ( tr ) )
else
base
else
new xsbti . api . Parameterized ( base , types ( in , args ) )
2011-05-25 14:07:34 +02:00
case SuperType ( thistpe : Type , supertpe : Type ) => warning ( "sbt-api: Super type (not implemented): this=" + thistpe + ", super=" + supertpe ) ; Constants . emptyType
2010-11-10 02:49:23 +01:00
case at : AnnotatedType => annotatedType ( in , at )
2010-10-23 03:55:16 +02:00
case rt : CompoundType => structure ( rt )
2010-11-10 02:49:23 +01:00
case ExistentialType ( tparams , result ) => new xsbti . api . Existential ( processType ( in , result ) , typeParameters ( in , tparams ) )
2011-05-25 13:57:14 +02:00
case NoType => Constants . emptyType // this can happen when there is an error that will be reported by a later phase
2010-11-10 02:49:23 +01:00
case PolyType ( typeParams , resultType ) => new xsbti . api . Polymorphic ( processType ( in , resultType ) , typeParameters ( in , typeParams ) )
2011-05-25 14:07:34 +02:00
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
2009-11-16 14:46:47 +01:00
}
}
2010-11-10 02:49:23 +01:00
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 =
2009-11-16 14:46:47 +01:00
{
val varianceInt = s . variance
import xsbti.api.Variance._
2010-11-10 02:49:23 +01:00
val annots = annotations ( in , s )
2009-11-16 14:46:47 +01:00
val variance = if ( varianceInt < 0 ) Contravariant else if ( varianceInt > 0 ) Covariant else Invariant
2010-11-10 02:49:23 +01:00
viewer ( in ) . memberInfo ( s ) match
2009-11-16 14:46:47 +01:00
{
2012-05-13 05:12:29 +02:00
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 ) )
2009-11-16 14:46:47 +01:00
case x => error ( "Unknown type parameter info: " + x . getClass )
}
}
2012-05-13 05:12:29 +02:00
private def tparamID ( s : Symbol ) = s . fullName
2011-03-11 22:57:15 +01:00
private def selfType ( in : Symbol , s : Symbol ) : xsbti . api . Type = processType ( in , s . thisSym . typeOfThis )
2010-10-23 03:55:16 +02:00
2010-11-10 02:49:23 +01:00
private def classLike ( in : Symbol , c : Symbol ) : ClassLike = classLikeCache . getOrElseUpdate ( ( in , c ) , mkClassLike ( in , c ) )
2010-10-23 03:55:16 +02:00
private def mkClassLike ( in : Symbol , c : Symbol ) : ClassLike =
2009-11-16 14:46:47 +01:00
{
2011-08-18 03:50:46 +02:00
val name = c . fullName
2009-11-16 14:46:47 +01:00
val isModule = c . isModuleClass || c . isModule
2010-11-10 02:49:23 +01:00
val struct = if ( isModule ) c . moduleClass else c
2009-11-16 14:46:47 +01:00
val defType =
if ( c . isTrait ) DefinitionType . Trait
else if ( isModule )
{
if ( c . isPackage ) DefinitionType . PackageModule
else DefinitionType . Module
}
else DefinitionType . ClassDef
2011-10-20 04:23:47 +02:00
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 ) )
2009-11-16 14:46:47 +01:00
}
private final class TopLevelHandler ( sourceFile : File ) extends TopLevelTraverser
{
val packages = new HashSet [ String ]
val definitions = new ListBuffer [ xsbti . api . Definition ]
2010-10-23 03:55:16 +02:00
def `class` ( c : Symbol ) : Unit = definitions += classLike ( c . owner , c )
2009-11-16 14:46:47 +01:00
/* * Record packages declared in the source file */
def `package` ( p : Symbol )
{
if ( ( p eq null ) || p == NoSymbol || p . isRoot || p . isRootPackage || p . isEmptyPackageClass || p . isEmptyPackage )
( )
else
{
2011-08-18 03:50:46 +02:00
packages += p . fullName
2009-11-16 14:46:47 +01:00
`package` ( p . enclosingPackage )
}
}
}
2012-05-01 02:34:48 +02:00
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
}
}
2009-11-16 14:46:47 +01:00
private object Constants
{
2010-01-01 00:55:35 +01:00
val local = new xsbti . api . ThisQualifier
2009-11-16 14:46:47 +01:00
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 )
def `package` ( s : Symbol )
override def traverse ( tree : Tree )
{
tree match
{
case ( _ : ClassDef | _ : ModuleDef ) if isTopLevel ( tree . symbol ) => `class` ( tree . symbol )
case p : PackageDef =>
`package` ( p . symbol )
super . traverse ( tree )
case _ =>
}
}
def isTopLevel ( sym : Symbol ) : Boolean =
( sym ne null ) && ( sym != NoSymbol ) && ! sym . isImplClass && ! sym . isNestedClass && sym . isStatic &&
2010-07-15 01:24:50 +02:00
! sym . hasFlag ( Flags . SYNTHETIC ) && ! sym . hasFlag ( Flags . JAVA )
2009-11-16 14:46:47 +01:00
}
2009-11-25 05:01:05 +01:00
2010-11-10 02:49:23 +01:00
private def annotations ( in : Symbol , s : Symbol ) : Array [ xsbti . api . Annotation ] =
2010-01-24 06:11:43 +01:00
atPhase ( currentRun . typerPhase ) {
2010-09-18 04:14:48 +02:00
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 )
2011-08-18 03:50:46 +02:00
associated . flatMap ( ss => annotations ( in , ss . annotations ) ) . distinct . toArray ;
2010-01-24 06:11:43 +01:00
}
2010-11-10 02:49:23 +01:00
private def annotatedType ( in : Symbol , at : AnnotatedType ) : xsbti . api . Type =
2010-01-08 03:41:20 +01:00
{
2011-08-18 03:50:46 +02:00
val annots = at . annotations
2010-11-10 02:49:23 +01:00
if ( annots . isEmpty ) processType ( in , at . underlying ) else annotated ( in , annots , at . underlying )
2010-01-08 03:41:20 +01:00
}
2011-03-11 22:57:15 +01:00
private def simpleName ( s : Symbol ) : String =
{
val n = s . originalName
val n2 = if ( n . toString == "<init>" ) n else n . decode
n2 . toString . trim
}
2010-10-23 03:55:16 +02:00
}