mirror of https://github.com/sbt/sbt.git
API equality testing. Still includes debugging statements.
This commit is contained in:
parent
8ebbb7b420
commit
833688cdd9
|
|
@ -6,7 +6,7 @@ The discovery phase provides a representation of the API in the form of a data s
|
|||
|
||||
The file that defines the hierarchy is processed by a small datatype generating subproject called 'datatype'. This subproject defines a basic syntax for creating an immutable data structure hierarchy in Java. The output is in Java in order to use the data structure across Scala versions.
|
||||
|
||||
Finally, the data structure needs to be persisted. This is currently unimplemented, but will likely use SBinary.
|
||||
Finally, the data structure needs to be persisted. This is currently implemented with standard Java serialization, but will likely use SBinary for a custom implementation for efficiency.
|
||||
|
||||
= API Specification =
|
||||
|
||||
|
|
@ -14,6 +14,10 @@ This section specifies what the API of a source file is. Familiarity with the s
|
|||
|
||||
Loosely, the API consists of the signatures in a source file. This includes packages and types of classes in a source. The type of a class includes its fully qualified name, type parameters, base classes, and member signatures.
|
||||
|
||||
Equality of APIs is determined by straightforward structural equality except for type parameter definitions and references.
|
||||
* The ID of a type parameter does not affect equality between two type parameters. The mapping between IDs of equal type parameters is recorded, however.
|
||||
* When comparing two type parameter references, the comparison is deferred until initial equality checking (all other equality checking) is done. Then, the IDs are compared according to the mapping determined for type parameters in the initial check.
|
||||
|
||||
== Top Level Constructs ==
|
||||
The top level contributions to the API in a source file are packages and top-level classes. Packages in Scala can be nested, but how they are nested is not important for the API of a source file. That only affects scope within that source file [VERIFY]. A top level class is a class that is not enclosed in another class or a method.
|
||||
|
||||
|
|
@ -114,7 +118,7 @@ The following sections briefly describe the contents of the different types.
|
|||
* The base type, which is a simple type
|
||||
|
||||
=== Structural ===
|
||||
* The base classes, in linearization order.
|
||||
* The base class types, in linearization order.
|
||||
* The declared members. These are definitions, described above.
|
||||
* The inherited members. These are definitions, described above.
|
||||
|
||||
|
|
@ -269,4 +273,6 @@ PathComponent
|
|||
|
||||
= Data Structure Generator =
|
||||
|
||||
= API Data Structure Persistence =
|
||||
= API Data Structure Persistence =
|
||||
|
||||
The data structure is persisted by standard Java serialization on Source. This is done for simplicity and will be changed to use a more efficient encoding.
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.api
|
||||
|
||||
import xsbti.api._
|
||||
|
||||
import Function.tupled
|
||||
import scala.collection.{immutable, mutable}
|
||||
|
||||
object SameAPI
|
||||
{
|
||||
def apply(a: Source, b: Source) =
|
||||
(new SameAPI).check(a,b)
|
||||
}
|
||||
private class SameAPI
|
||||
{
|
||||
def debug(flag: Boolean, msg: => String): Boolean =
|
||||
{
|
||||
if(!flag) println(msg)
|
||||
flag
|
||||
}
|
||||
|
||||
private val parameterMap = new mutable.HashMap[Int, Int]
|
||||
private val referencesMap = new mutable.HashMap[Int, Int]
|
||||
|
||||
private def mapID(map: mutable.Map[Int, Int], key: Int, value: Int): Boolean =
|
||||
equalOrEmpty(map.put(key, value), value)
|
||||
private def equalOrEmpty[T](o: Option[T], nonemptyValue: T): Boolean =
|
||||
o match { case Some(v) => v == nonemptyValue; case None => true }
|
||||
|
||||
def check(a: Source, b: Source) =
|
||||
samePackages(a, b) &&
|
||||
debug(sameDefinitions(a, b), "Definitions differed") &&
|
||||
debug(sameReferences, "References differed")
|
||||
|
||||
def sameReferences: Boolean =
|
||||
referencesMap.forall(tupled(sameReference))
|
||||
def sameReference(a: Int, b: Int): Boolean =
|
||||
parameterMap(a) == b
|
||||
|
||||
def samePackages(a: Source, b: Source): Boolean =
|
||||
sameStrings(packages(a), packages(b))
|
||||
def packages(s: Source): Set[String] =
|
||||
Set() ++ s.packages.map(_.name)
|
||||
|
||||
def sameDefinitions(a: Source, b: Source): Boolean =
|
||||
sameDefinitions(a.definitions, b.definitions)
|
||||
def sameDefinitions(a: Seq[Definition], b: Seq[Definition]): Boolean =
|
||||
{
|
||||
val (avalues, atypes) = separateDefinitions(a)
|
||||
val (bvalues, btypes) = separateDefinitions(b)
|
||||
debug(sameDefinitions(byName(avalues), byName(bvalues)), "Value definitions differed") &&
|
||||
debug(sameDefinitions(byName(atypes), byName(btypes)), "Type definitions differed")
|
||||
}
|
||||
def separateDefinitions(s: Seq[Definition]): (Seq[Definition], Seq[Definition]) =
|
||||
s.toArray.partition(isValueDefinition)
|
||||
def sameDefinitions(a: scala.collection.Map[String, List[Definition]], b: scala.collection.Map[String, List[Definition]]): Boolean =
|
||||
debug(sameStrings(a.keySet, b.keySet), "\tDefinition strings differed") && zippedEntries(a,b).forall(tupled(sameNamedDefinitions))
|
||||
|
||||
def sameNamedDefinitions(a: List[Definition], b: List[Definition]): Boolean =
|
||||
{
|
||||
def sameDefs(a: List[Definition], b: List[Definition]): Boolean =
|
||||
a match
|
||||
{
|
||||
case xdef :: tail =>
|
||||
b.find(bdef => sameDefinitionContent(xdef, bdef)) match
|
||||
{
|
||||
case Some(bdef) => sameDefs(tail, b - bdef)
|
||||
case None => debug(false, "Definition not in new API: \n" + xdef.name )
|
||||
}
|
||||
case Nil => true
|
||||
}
|
||||
debug((a.length == b.length), "\t\tLength differed for " + a.headOption.map(_.name).getOrElse("empty")) && sameDefs(a, b)
|
||||
}
|
||||
|
||||
def isValueDefinition(d: Definition): Boolean =
|
||||
d match
|
||||
{
|
||||
case _: FieldLike | _: Def=> true
|
||||
case c: ClassLike => isValue(c.definitionType)
|
||||
case _ => false
|
||||
}
|
||||
def isValue(d: DefinitionType): Boolean =
|
||||
d == DefinitionType.Module || d == DefinitionType.PackageModule
|
||||
def byName(s: Seq[Definition]): scala.collection.Map[String, List[Definition]] =
|
||||
{
|
||||
val map = new mutable.HashMap[String, List[Definition]]
|
||||
for(d <- s; name = d.name)
|
||||
map(name) = d :: map.getOrElse(name, Nil)
|
||||
map.readOnly
|
||||
}
|
||||
|
||||
// doesn't check name
|
||||
def sameDefinitionContent(a: Definition, b: Definition): Boolean =
|
||||
//a.name == b.name &&
|
||||
debug(sameAccess(a.access, b.access), "Access differed") &&
|
||||
debug(sameModifiers(a.modifiers, b.modifiers), "Modifiers differed") &&
|
||||
debug(sameAnnotations(a.annotations, b.annotations), "Annotations differed") &&
|
||||
debug(sameDefinitionSpecificAPI(a, b), "Definition-specific differed")
|
||||
|
||||
def sameAccess(a: Access, b: Access): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (_: Public, _: Public) => true
|
||||
case (qa: Protected, qb: Protected) => sameQualifier(qa, qb)
|
||||
case (qa: Private, qb: Private) => sameQualifier(qa, qb)
|
||||
case (qa: Pkg, qb: Pkg) => sameQualifier(qa, qb)
|
||||
case _ => debug(false, "Different access categories")
|
||||
}
|
||||
def sameQualifier(a: Qualified, b: Qualified): Boolean =
|
||||
sameQualifier(a.qualifier, b.qualifier)
|
||||
def sameQualifier(a: Qualifier, b: Qualifier): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (_: Unqualified, _: Unqualified) => true
|
||||
case (_: ThisQualifier, _: ThisQualifier) => true
|
||||
case (ia: IdQualifier, ib: IdQualifier) => debug(ia.value == ib.value, "Different qualifiers")
|
||||
case _ => debug(false, "Different qualifier categories: " + a.getClass.getName + " -- " +b.getClass.getName)
|
||||
}
|
||||
|
||||
def sameModifiers(a: Modifiers, b: Modifiers): Boolean =
|
||||
bitSet(a) == bitSet(b)
|
||||
|
||||
def bitSet(m: Modifiers): immutable.BitSet =
|
||||
{
|
||||
import m._
|
||||
val bs = new mutable.BitSet
|
||||
setIf(bs, isAbstract, 0)
|
||||
setIf(bs, isDeferred, 1)
|
||||
setIf(bs, isOverride, 2)
|
||||
setIf(bs, isFinal, 3)
|
||||
setIf(bs, isSealed, 4)
|
||||
setIf(bs, isImplicit, 5)
|
||||
setIf(bs, isLazy, 6)
|
||||
setIf(bs, isSynthetic, 7)
|
||||
bs.toImmutable
|
||||
}
|
||||
def setIf(bs: mutable.BitSet, flag: Boolean, i: Int): Unit =
|
||||
if(flag) bs += i
|
||||
|
||||
def sameAnnotations(a: Seq[Annotation], b: Seq[Annotation]): Boolean =
|
||||
sameSeq(a, b)(sameAnnotation)
|
||||
def sameAnnotation(a: Annotation, b: Annotation): Boolean =
|
||||
sameSimpleType(a.base, b.base) &&
|
||||
sameSeq(a.arguments, b.arguments)(defaultEquals)
|
||||
|
||||
def sameDefinitionSpecificAPI(a: Definition, b: Definition): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (fa: FieldLike, fb: FieldLike) => sameFieldSpecificAPI(fa, fb)
|
||||
case (pa: ParameterizedDefinition, pb: ParameterizedDefinition) => sameParameterizedDefinition(pa, pb)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def sameParameterizedDefinition(a: ParameterizedDefinition, b: ParameterizedDefinition): Boolean =
|
||||
debug(sameTypeParameters(a.typeParameters, b.typeParameters), "Different type parameters for " + a.name) &&
|
||||
sameParameterizedSpecificAPI(a, b)
|
||||
|
||||
def sameParameterizedSpecificAPI(a: ParameterizedDefinition, b: ParameterizedDefinition): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (da: Def, db: Def) => sameDefSpecificAPI(da, db)
|
||||
case (ca: ClassLike, cb: ClassLike) => sameClassLikeSpecificAPI(ca, cb)
|
||||
case (ta: TypeAlias, tb: TypeAlias) => sameAliasSpecificAPI(ta, tb)
|
||||
case (ta: TypeDeclaration, tb: TypeDeclaration) => sameDeclarationSpecificAPI(ta, tb)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def sameDefSpecificAPI(a: Def, b: Def): Boolean =
|
||||
debug(sameValueParameters(a.valueParameters, b.valueParameters), "Different def value parameters for " + a.name) &&
|
||||
debug(sameType(a.returnType, b.returnType), "Different def return type for " + a.name)
|
||||
def sameAliasSpecificAPI(a: TypeAlias, b: TypeAlias): Boolean =
|
||||
debug(sameType(a.tpe, b.tpe), "Different alias type for " + a.name)
|
||||
def sameDeclarationSpecificAPI(a: TypeDeclaration, b: TypeDeclaration): Boolean =
|
||||
debug(sameType(a.lowerBound, b.lowerBound), "Different lower bound for declaration " + a.name) &&
|
||||
debug(sameType(a.upperBound, b.upperBound), "Different upper bound for declaration " + a.name)
|
||||
def sameFieldSpecificAPI(a: FieldLike, b: FieldLike): Boolean =
|
||||
debug(sameFieldCategory(a, b), "Different field categories (" + a.name + "=" + a.getClass.getName + " -- " +a.name + "=" + a.getClass.getName + ")")&&
|
||||
debug(sameType(a.tpe, b.tpe), "Different field type for " + a.name)
|
||||
|
||||
def sameFieldCategory(a: FieldLike, b: FieldLike): Boolean =
|
||||
(a,b) match
|
||||
{
|
||||
case (_: Val, _: Val) => true
|
||||
case (_: Var, _: Var) => true
|
||||
case _=> false
|
||||
}
|
||||
|
||||
def sameClassLikeSpecificAPI(a: ClassLike, b: ClassLike): Boolean =
|
||||
sameDefinitionType(a.definitionType, b.definitionType) &&
|
||||
sameType(a.selfType, b.selfType) &&
|
||||
sameStructure(a.structure, b.structure)
|
||||
|
||||
def sameValueParameters(a: Seq[ParameterList], b: Seq[ParameterList]): Boolean =
|
||||
sameSeq(a, b)(sameParameterList)
|
||||
|
||||
def sameParameterList(a: ParameterList, b: ParameterList): Boolean =
|
||||
(a.isImplicit == b.isImplicit) &&
|
||||
sameParameters(a.parameters, b.parameters)
|
||||
def sameParameters(a: Seq[MethodParameter], b: Seq[MethodParameter]): Boolean =
|
||||
sameSeq(a, b)(sameMethodParameter)
|
||||
def sameMethodParameter(a: MethodParameter, b: MethodParameter): Boolean =
|
||||
(a.name == b.name) &&
|
||||
sameType(a.tpe, b.tpe) &&
|
||||
(a.hasDefault == b.hasDefault) &&
|
||||
sameParameterModifier(a.modifier, b.modifier)
|
||||
def sameParameterModifier(a: ParameterModifier, b: ParameterModifier) =
|
||||
a == b
|
||||
def sameDefinitionType(a: DefinitionType, b: DefinitionType): Boolean =
|
||||
a == b
|
||||
def sameVariance(a: Variance, b: Variance): Boolean =
|
||||
a == b
|
||||
|
||||
def sameTypeParameters(a: Seq[TypeParameter], b: Seq[TypeParameter]): Boolean =
|
||||
debug(sameSeq(a, b)(sameTypeParameter), "Different type parameters")
|
||||
def sameTypeParameter(a: TypeParameter, b: TypeParameter): Boolean =
|
||||
sameTypeParameters(a.typeParameters, b.typeParameters) &&
|
||||
debug(sameVariance(a.variance, b.variance), "Different variance") &&
|
||||
debug(sameType(a.lowerBound, b.lowerBound), "Different lower bound") &&
|
||||
debug(sameType(a.upperBound, b.upperBound), "Different upper bound") &&
|
||||
debug(mapSameParameters(a, b), "Different type parameter bindings")
|
||||
|
||||
def mapSameParameters(a: TypeParameter, b: TypeParameter): Boolean =
|
||||
mapID(parameterMap, a.id, b.id)
|
||||
|
||||
def sameType(a: Type, b: Type): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (sa: SimpleType, sb: SimpleType) => debug(sameSimpleType(sa, sb), "Different simple type")
|
||||
case (aa: Annotated, ab: Annotated) => debug(sameAnnotatedType(aa, ab), "Different annotated type")
|
||||
case (sa: Structure, sb: Structure) => debug(sameStructure(sa, sb), "Different structure type")
|
||||
case (ea: Existential, eb: Existential) => debug(sameExistentialType(ea, eb), "Different existential type")
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def sameExistentialType(a: Existential, b: Existential): Boolean =
|
||||
sameTypeParameters(a.clause, b.clause) &&
|
||||
sameType(a.baseType, b.baseType)
|
||||
def sameAnnotatedType(a: Annotated, b: Annotated): Boolean =
|
||||
sameSimpleType(a.baseType, b.baseType) &&
|
||||
sameAnnotations(a.annotations, b.annotations)
|
||||
def sameStructure(a: Structure, b: Structure): Boolean =
|
||||
sameSeq(a.parents, b.parents)(sameType) &&
|
||||
sameMembers(a.declarations, b.declarations) &&
|
||||
sameMembers(a.inherited, b.inherited)
|
||||
|
||||
def sameMembers(a: Seq[Definition], b: Seq[Definition]): Boolean =
|
||||
sameDefinitions(a, b)
|
||||
|
||||
def sameSimpleType(a: SimpleType, b: SimpleType): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (pa: Projection, pb: Projection) => debug(sameProjection(pa, pb), "Different projection")
|
||||
case (pa: ParameterRef, pb: ParameterRef) => debug(sameParameterRef(pa, pb), "Different parameter ref")
|
||||
case (sa: Singleton, sb: Singleton) => debug(sameSingleton(sa, sb), "Different singleton")
|
||||
case (_: EmptyType, _: EmptyType) => true
|
||||
case (pa: Parameterized, pb: Parameterized) => debug(sameParameterized(pa, pb), "Different parameterized")
|
||||
case _ => debug(false, "Different category of simple type")
|
||||
}
|
||||
|
||||
def sameParameterized(a: Parameterized, b: Parameterized): Boolean =
|
||||
sameSimpleType(a.baseType, b.baseType) &&
|
||||
sameSeq(a.typeArguments, b.typeArguments)(sameSimpleType)
|
||||
def sameParameterRef(a: ParameterRef, b: ParameterRef): Boolean =
|
||||
mapID(referencesMap, a.id, b.id)
|
||||
def sameSingleton(a: Singleton, b: Singleton): Boolean =
|
||||
samePath(a.path, b.path)
|
||||
def sameProjection(a: Projection, b: Projection): Boolean =
|
||||
sameSimpleType(a.prefix, b.prefix) &&
|
||||
(a.id == b.id)
|
||||
|
||||
def samePath(a: Path, b: Path): Boolean =
|
||||
samePathComponents(a.components, b.components)
|
||||
def samePathComponents(a: Seq[PathComponent], b: Seq[PathComponent]): Boolean =
|
||||
sameSeq(a, b)(samePathComponent)
|
||||
def samePathComponent(a: PathComponent, b: PathComponent): Boolean =
|
||||
(a, b) match
|
||||
{
|
||||
case (_: This, _: This) => true
|
||||
case (sa: Super, sb: Super) => samePathSuper(sa, sb)
|
||||
case (ia: Id, ib: Id) => samePathId(ia, ib)
|
||||
case _ => false
|
||||
}
|
||||
def samePathSuper(a: Super, b: Super): Boolean =
|
||||
samePath(a.qualifier, b.qualifier)
|
||||
def samePathId(a: Id, b: Id): Boolean =
|
||||
a.id == b.id
|
||||
|
||||
// precondition: a.keySet == b.keySet
|
||||
protected def zippedEntries[A,B](a: scala.collection.Map[A,B], b: scala.collection.Map[A,B]): Iterable[(B,B)] =
|
||||
for( (key, avalue) <- a) yield (avalue, b(key))
|
||||
|
||||
def sameStrings(a: scala.collection.Set[String], b: scala.collection.Set[String]) =
|
||||
a == b
|
||||
def sameSeq[T](a: Seq[T], b: Seq[T])(eq: (T,T) => Boolean): Boolean =
|
||||
sameArray(a.toArray, b.toArray)(eq)
|
||||
private def sameArray[T](a: Array[T], b: Array[T])(eq: (T,T) => Boolean): Boolean =
|
||||
(a.length == b.length) && a.zip(b).forall(tupled(eq))
|
||||
|
||||
def defaultEquals[T <: AnyRef] = (a: T, b: T) => a == b
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
|
|||
val launchSub = project(launchPath, "Launcher", new LaunchProject(_), launchInterfaceSub)
|
||||
|
||||
val interfaceSub = project("interface", "Interface", new InterfaceProject(_))
|
||||
val apiSub = baseProject(compilePath / "api", "API", interfaceSub)
|
||||
|
||||
val controlSub = baseProject(utilPath / "control", "Control")
|
||||
val collectionSub = baseProject(utilPath / "collection", "Collections")
|
||||
|
|
|
|||
Loading…
Reference in New Issue