Skip to content

Commit

Permalink
Merge pull request #116 from gkossakowski/inherited-classes
Browse files Browse the repository at this point in the history
Fix API extraction of inherited classes
  • Loading branch information
eed3si9n committed May 4, 2016
2 parents 84942e0 + 459df6b commit 1f07510
Show file tree
Hide file tree
Showing 18 changed files with 301 additions and 251 deletions.
15 changes: 15 additions & 0 deletions internal/compiler-bridge/src-2.10/main/scala/xsbt/ClassName.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ trait ClassName {
*/
protected def className(s: Symbol): String = pickledName(s)

/**
* Create a (source) name for the class symbol `s` with a prefix determined by the class symbol `in`.
*
* If `s` represents a package object `pkg3`, then the returned name will be `pkg1.pkg2.pkg3.package`.
* If `s` represents a class `Foo` nested in package object `pkg3` then the returned name is `pkg1.pkg2.pk3.Foo`.
*/
protected def classNameAsSeenIn(in: Symbol, s: Symbol): String = atPhase(currentRun.picklerPhase.next) {
if (in.isRoot || in.isRootPackage || in == NoSymbol || in.isEffectiveRoot)
s.simpleName.toString
else if (in.isPackageObjectOrClass)
in.owner.fullName + "." + s.name
else
in.fullName + "." + s.name
}

private def pickledName(s: Symbol): String =
atPhase(currentRun.picklerPhase) { s.fullName }

Expand Down
43 changes: 17 additions & 26 deletions internal/compiler-bridge/src-2.10/main/scala/xsbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ExtractAPI[GlobalType <: Global](
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 classLikeCache = new HashMap[(Symbol, Symbol), xsbti.api.ClassLikeDef]
private[this] val pending = new HashSet[xsbti.api.Lazy[_]]

private[this] val emptyStringArray = new Array[String](0)
Expand Down Expand Up @@ -341,29 +341,17 @@ class ExtractAPI[GlobalType <: Global](
// but that does not take linearization into account.
def linearizedAncestorTypes(info: Type): List[Type] = info.baseClasses.tail.map(info.baseType)

/*
* Create structure without any members. This is used to declare an inner class as a member of other class
* but to not include its full api. Class signature is enough.
*/
private def mkStructureWithEmptyMembers(info: Type, s: Symbol): xsbti.api.Structure = {
// We're not interested in the full linearization, so we can just use `parents`,
// which side steps issues with baseType when f-bounded existential types and refined types mix
// (and we get cyclic types which cause a stack overflow in showAPI).
val parentTypes = info.parents
mkStructure(s, parentTypes, Nil, Nil)
}

private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure = {
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] =
private def processDefinitions(in: Symbol, defs: List[Symbol]): Array[xsbti.api.ClassDefinition] =
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] =
private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.ClassDefinition] =
{
def mkVar = Some(fieldDef(in, sym, false, new xsbti.api.Var(_, _, _, _, _)))
def mkVal = Some(fieldDef(in, sym, true, new xsbti.api.Val(_, _, _, _, _)))
Expand Down Expand Up @@ -549,8 +537,8 @@ class ExtractAPI[GlobalType <: Global](
allNonLocalClassesInSrc.toSet
}

private def classLike(in: Symbol, c: Symbol): ClassLike = classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c))
private def mkClassLike(in: Symbol, c: Symbol): ClassLike = {
private def classLike(in: Symbol, c: Symbol): ClassLikeDef = classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c))
private def mkClassLike(in: Symbol, c: Symbol): ClassLikeDef = {
// Normalize to a class symbol, and initialize it.
// (An object -- aka module -- also has a term symbol,
// but it's the module class that holds the info about its structure.)
Expand All @@ -563,23 +551,26 @@ class ExtractAPI[GlobalType <: Global](
} else DefinitionType.ClassDef
val childrenOfSealedClass = sort(sym.children.toArray).map(c => processType(c, c.tpe))
val topLevel = sym.owner.isPackageClass
val anns = annotations(in, c)
val modifiers = getModifiers(c)
val acc = getAccess(c)
val name = classNameAsSeenIn(in, c)
val tParams = typeParameters(in, sym) // look at class symbol
val selfType = lzy(this.selfType(in, sym))
def constructClass(structure: xsbti.api.Lazy[Structure]): ClassLike = {
new xsbti.api.ClassLike(
defType, lzy(selfType(in, sym)), structure, emptyStringArray,
childrenOfSealedClass, topLevel, typeParameters(in, sym), // look at class symbol
className(c), getAccess(c), getModifiers(c), annotations(in, c)
) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff
new xsbti.api.ClassLike(defType, selfType, structure, emptyStringArray,
childrenOfSealedClass, topLevel, tParams, name, acc, modifiers, anns) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff
}

val info = viewer(in).memberInfo(sym)
val structure = lzy(structureWithInherited(info, sym))
val classWithMembers = constructClass(structure)
val structureWithoutMembers = lzy(mkStructureWithEmptyMembers(info, sym))
val classWithoutMembers = constructClass(structureWithoutMembers)

allNonLocalClassesInSrc += classWithMembers

classWithoutMembers
val classDef = new xsbti.api.ClassLikeDef(
defType, tParams, name, acc, modifiers, anns
) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff
classDef
}

// TODO: could we restrict ourselves to classes, ignoring the term symbol for modules,
Expand Down
15 changes: 15 additions & 0 deletions internal/compiler-bridge/src/main/scala/xsbt/ClassName.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ trait ClassName {
*/
protected def className(s: Symbol): String = pickledName(s)

/**
* Create a (source) name for the class symbol `s` with a prefix determined by the class symbol `in`.
*
* If `s` represents a package object `pkg3`, then the returned name will be `pkg1.pkg2.pkg3.package`.
* If `s` represents a class `Foo` nested in package object `pkg3` then the returned name is `pkg1.pkg2.pk3.Foo`.
*/
protected def classNameAsSeenIn(in: Symbol, s: Symbol): String = enteringPhase(currentRun.picklerPhase.next) {
if (in.isRoot || in.isRootPackage || in == NoSymbol || in.isEffectiveRoot)
s.simpleName.toString
else if (in.isPackageObjectOrClass)
in.owner.fullName + "." + s.name
else
in.fullName + "." + s.name
}

private def pickledName(s: Symbol): String =
enteringPhase(currentRun.picklerPhase.next) { s.fullName }

Expand Down
45 changes: 18 additions & 27 deletions internal/compiler-bridge/src/main/scala/xsbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ExtractAPI[GlobalType <: Global](
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 classLikeCache = new HashMap[(Symbol, Symbol), xsbti.api.ClassLikeDef]
private[this] val pending = new HashSet[xsbti.api.Lazy[_]]

private[this] val emptyStringArray = new Array[String](0)
Expand Down Expand Up @@ -341,29 +341,17 @@ class ExtractAPI[GlobalType <: Global](
// but that does not take linearization into account.
def linearizedAncestorTypes(info: Type): List[Type] = info.baseClasses.tail.map(info.baseType)

/*
* Create structure without any members. This is used to declare an inner class as a member of other class
* but to not include its full api. Class signature is enough.
*/
private def mkStructureWithEmptyMembers(info: Type, s: Symbol): xsbti.api.Structure = {
// We're not interested in the full linearization, so we can just use `parents`,
// which side steps issues with baseType when f-bounded existential types and refined types mix
// (and we get cyclic types which cause a stack overflow in showAPI).
val parentTypes = info.parents
mkStructure(s, parentTypes, Nil, Nil)
}

private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure = {
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] =
private def processDefinitions(in: Symbol, defs: List[Symbol]): Array[xsbti.api.ClassDefinition] =
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] =
private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.ClassDefinition] =
{
def mkVar = Some(fieldDef(in, sym, false, new xsbti.api.Var(_, _, _, _, _)))
def mkVal = Some(fieldDef(in, sym, true, new xsbti.api.Val(_, _, _, _, _)))
Expand Down Expand Up @@ -549,8 +537,8 @@ class ExtractAPI[GlobalType <: Global](
allNonLocalClassesInSrc.toSet
}

private def classLike(in: Symbol, c: Symbol): ClassLike = classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c))
private def mkClassLike(in: Symbol, c: Symbol): ClassLike = {
private def classLike(in: Symbol, c: Symbol): ClassLikeDef = classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c))
private def mkClassLike(in: Symbol, c: Symbol): ClassLikeDef = {
// Normalize to a class symbol, and initialize it.
// (An object -- aka module -- also has a term symbol,
// but it's the module class that holds the info about its structure.)
Expand All @@ -563,23 +551,26 @@ class ExtractAPI[GlobalType <: Global](
} else DefinitionType.ClassDef
val childrenOfSealedClass = sort(sym.children.toArray).map(c => processType(c, c.tpe))
val topLevel = sym.owner.isPackageClass
val anns = annotations(in, c)
val modifiers = getModifiers(c)
val acc = getAccess(c)
val name = classNameAsSeenIn(in, c)
val tParams = typeParameters(in, sym) // look at class symbol
val selfType = lzy(this.selfType(in, sym))
def constructClass(structure: xsbti.api.Lazy[Structure]): ClassLike = {
new xsbti.api.ClassLike(
defType, lzy(selfType(in, sym)), structure, emptyStringArray,
childrenOfSealedClass, topLevel, typeParameters(in, sym), // look at class symbol
className(c), getAccess(c), getModifiers(c), annotations(in, c)
) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff
new xsbti.api.ClassLike(defType, selfType, structure, emptyStringArray,
childrenOfSealedClass, topLevel, tParams, name, acc, modifiers, anns) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff
}

val info = viewer(in).memberInfo(sym)
val structure = lzy(structureWithInherited(info, sym))
val classWithMembers = constructClass(structure)
val structureWithoutMembers = lzy(mkStructureWithEmptyMembers(info, sym))
val classWithoutMembers = constructClass(structureWithoutMembers)

allNonLocalClassesInSrc += classWithMembers

classWithoutMembers
val classDef = new xsbti.api.ClassLikeDef(
defType, tParams, name, acc, modifiers, anns
) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff
classDef
}

// TODO: could we restrict ourselves to classes, ignoring the term symbol for modules,
Expand Down Expand Up @@ -632,4 +623,4 @@ class ExtractAPI[GlobalType <: Global](
implicit def compat(ann: AnnotationInfo): IsStatic = new IsStatic(ann)
annotations.filter(_.isStatic)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,8 @@ class ExtractAPISpecification extends UnitSpec {
*/
it should "make a stable representation of a self variable that has no self type" in {
def selectNamer(apis: Set[ClassLike]): ClassLike = {
def selectClass(defs: Iterable[Definition], name: String): ClassLike = defs.collectFirst {
case cls: ClassLike if cls.name == name => cls
}.get
val global = apis.find(_.name == "Global").get
//val foo = selectClass(global.structure.declared, "Global.Foo")
val foo = apis.find(_.name == "Global.Foo").get
selectClass(foo.structure.inherited, "Namers.Namer")
// TODO: this doesn't work yet because inherited classes are not extracted
apis.find(_.name == "Global.Foo.Namer").get
}
val src1 =
"""|class Namers {
Expand All @@ -150,6 +145,33 @@ class ExtractAPISpecification extends UnitSpec {
assert(SameAPI(namerApi1, namerApi2))
}

it should "make a different representation for an inherited class" in {
val src =
"""|class A[T] {
| abstract class AA { def t: T }
|}
|class B extends A[Int]
""".stripMargin
val compilerForTesting = new ScalaCompilerForUnitTesting
val apis = compilerForTesting.extractApisFromSrc(src).map(a => a.name -> a).toMap
assert(apis.keySet === Set("A", "A.AA", "B", "B.AA"))
assert(apis("A.AA") !== apis("B.AA"))
}

it should "handle package objects and type companions" in {
val src =
"""|package object abc {
| type BuildInfoKey = BuildInfoKey.Entry[_]
| object BuildInfoKey {
| sealed trait Entry[A]
| }
|}
""".stripMargin
val compilerForTesting = new ScalaCompilerForUnitTesting
val apis = compilerForTesting.extractApisFromSrc(src).map(a => a.name -> a).toMap
assert(apis.keySet === Set("abc.package", "abc.BuildInfoKey", "abc.BuildInfoKey.Entry"))
}

/**
* Checks if self type is properly extracted in various cases of declaring a self type
* with our without a self variable.
Expand Down
Loading

0 comments on commit 1f07510

Please sign in to comment.