sbt/compile/api/SameAPI.scala

362 lines
15 KiB
Scala
Raw Normal View History

/* 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) =
2010-01-08 03:48:56 +01:00
{
val start = System.currentTimeMillis
val result = (new SameAPI).check(a,b)
val end = System.currentTimeMillis
println(" API comparison took: " + (end - start) / 1000.0 + " s")
result
}
}
private class SameAPI
{
abstract class Result(val referencesMap: immutable.Map[Int, Int], val parameterMap: immutable.Map[Int, Int]) extends NotNull
{
def &&(o: => Result): Result
def value: Boolean
}
final object False extends Result(Map.empty, Map.empty)
{
def &&(o: => Result): Result = this
def value = false
}
final class True(references: immutable.Map[Int, Int], parameters: immutable.Map[Int, Int]) extends Result(references, parameters)
{
def value = true
def &&(o: => Result): Result = o match { case False => o; case t: True => newTrue(t) }
private def newTrue(t: True): True =
{
def equalIntersection(a: Map[Int, Int], b: Map[Int, Int]) =
{
val sameKeys = a.keySet ** b.keySet
assume(sameKeys.forall(key => a(key) == b(key)))
}
equalIntersection(referencesMap, t.referencesMap)
equalIntersection(parameterMap, t.parameterMap)
new True(referencesMap ++ t.referencesMap, parameterMap ++ t.parameterMap)
}
}
implicit def b2b(b: Boolean): Result = if(b) new True(Map.empty, Map.empty) else False
def debug(flag: Result, msg: => String): Result =
{
if(!flag.value) println(msg)
flag
}
def printMaps(b: Result) = println(mapStrings(b))
def mapStrings(b: Result) =
{
"parameter map:\n\t" +
b.parameterMap.mkString("\n\t") +
"\nreference map:\n\t" +
b.referencesMap.mkString("\n\t")
}
def check(a: Source, b: Source): Boolean =
{
val initialResult =
samePackages(a, b) &&
debug(sameDefinitions(a, b), "Definitions differed")
initialResult.value &&
debug(sameReferences(initialResult.referencesMap, initialResult.parameterMap), "References differed: \n" + mapStrings(initialResult)).value
}
def sameReferences(referencesMap: Map[Int,Int], parameterMap: Map[Int,Int]): Boolean =
referencesMap.forall { case (a, b) => parameterMap(a) == b }
def samePackages(a: Source, b: Source): Result =
sameStrings(packages(a), packages(b))
def packages(s: Source): Set[String] =
Set() ++ s.packages.map(_.name)
def sameDefinitions(a: Source, b: Source): Result =
sameDefinitions(a.definitions, b.definitions)
def sameDefinitions(a: Seq[Definition], b: Seq[Definition]): Result =
{
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]]): Result =
debug(sameStrings(a.keySet, b.keySet), "\tDefinition strings differed") && forall(zippedEntries(a,b))(tupled(sameNamedDefinitions))
def sameNamedDefinitions(a: List[Definition], b: List[Definition]): Result =
{
println("Comparing " + a.size + " defs against " + b.size + " defs")
def sameDefs(a: List[Definition], b: List[Definition], v: Result): Result =
{
println("\t " + a.size + " to " + b.size + "")
a match
{
case adef :: atail =>
def sameDef(seen: List[Definition], remaining: List[Definition]): Result =
remaining match
{
case Nil => debug(false, "Definition different in new API: \n" + adef.name )
case bdef :: btail =>
val eq = v && sameDefinitionContent(adef, bdef)
if(eq.value) printMaps(eq)
if(eq.value) sameDefs(atail, seen ::: btail, eq) else sameDef(bdef :: seen, btail)
}
sameDef(Nil, b)
case Nil => v
}
}
//if(a.length > 1) println("Comparing\n" + a.mkString("\n\t") + "\nagainst\n" + b.mkString("\n\t") + "\n\n")
debug((a.length == b.length), "\t\tLength differed for " + a.headOption.map(_.name).getOrElse("empty")) && sameDefs(a, b, true)
}
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): Result =
//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): Result =
(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): Result =
sameQualifier(a.qualifier, b.qualifier)
def sameQualifier(a: Qualifier, b: Qualifier): Result =
(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): Result =
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: Result, i: Int): Unit =
if(flag.value) bs += i
def sameAnnotations(a: Seq[Annotation], b: Seq[Annotation]): Result =
sameSeq(a, b)(sameAnnotation)
def sameAnnotation(a: Annotation, b: Annotation): Result =
debug(sameSimpleType(a.base, b.base), "Annotation base type differed") &&
debug(sameAnnotationArguments(a.arguments, b.arguments), "Annotation arguments differed (" + a + ") and (" + b + ")")
def sameAnnotationArguments(a: Seq[AnnotationArgument], b: Seq[AnnotationArgument]): Result =
argumentMap(a) == argumentMap(b)
def argumentMap(a: Seq[AnnotationArgument]): Map[String,String] =
Map() ++ a.map(arg => (arg.name, arg.value))
def sameDefinitionSpecificAPI(a: Definition, b: Definition): Result =
(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): Result =
debug(sameTypeParameters(a.typeParameters, b.typeParameters), "Different type parameters for " + a.name) &&
sameParameterizedSpecificAPI(a, b)
def sameParameterizedSpecificAPI(a: ParameterizedDefinition, b: ParameterizedDefinition): Result =
(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): Result =
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): Result =
debug(sameType(a.tpe, b.tpe), "Different alias type for " + a.name)
def sameDeclarationSpecificAPI(a: TypeDeclaration, b: TypeDeclaration): Result =
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): Result =
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): Result =
(a,b) match
{
case (_: Val, _: Val) => true
case (_: Var, _: Var) => true
case _=> false
}
def sameClassLikeSpecificAPI(a: ClassLike, b: ClassLike): Result =
sameDefinitionType(a.definitionType, b.definitionType) &&
sameType(a.selfType, b.selfType) &&
sameStructure(a.structure, b.structure)
def sameValueParameters(a: Seq[ParameterList], b: Seq[ParameterList]): Result =
sameSeq(a, b)(sameParameterList)
def sameParameterList(a: ParameterList, b: ParameterList): Result =
((a.isImplicit == b.isImplicit): Result) &&
sameParameters(a.parameters, b.parameters)
def sameParameters(a: Seq[MethodParameter], b: Seq[MethodParameter]): Result =
sameSeq(a, b)(sameMethodParameter)
def sameMethodParameter(a: MethodParameter, b: MethodParameter): Result =
((a.name == b.name): Result) &&
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): Result =
a == b
def sameVariance(a: Variance, b: Variance): Result =
a == b
def sameTypeParameters(a: Seq[TypeParameter], b: Seq[TypeParameter]): Result =
debug(sameSeq(a, b)(sameTypeParameter), "Different type parameters")
def sameTypeParameter(a: TypeParameter, b: TypeParameter): Result =
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): Result =
mapParameters(a.id, b.id)
def mapParameters(a: Int, b: Int) = new True(Map.empty, Map( a -> b ))
def sameType(a: Type, b: Type): Result =
(a, b) match
{
case (sa: SimpleType, sb: SimpleType) => debug(sameSimpleType(sa, sb), "Different simple types: " + sa + " and " + {Thread.dumpStack; sb})
case (aa: Annotated, ab: Annotated) => debug(sameAnnotatedType(aa, ab), "Different annotated types")
case (sa: Structure, sb: Structure) => debug(sameStructure(sa, sb), "Different structure type")
case (ea: Existential, eb: Existential) => debug(sameExistentialType(ea, eb), "Different existential type")
2010-01-08 03:48:56 +01:00
case (pa: Polymorphic, pb: Polymorphic) => debug(samePolymorphicType(pa, pb), "Different polymorphic type")
case _ => false
}
def sameExistentialType(a: Existential, b: Existential): Result =
sameTypeParameters(a.clause, b.clause) &&
sameType(a.baseType, b.baseType)
def samePolymorphicType(a: Polymorphic, b: Polymorphic): Result =
2010-01-08 03:48:56 +01:00
sameTypeParameters(a.parameters, b.parameters) &&
sameType(a.baseType, b.baseType)
def sameAnnotatedType(a: Annotated, b: Annotated): Result =
sameSimpleType(a.baseType, b.baseType) &&
sameAnnotations(a.annotations, b.annotations)
def sameStructure(a: Structure, b: Structure): Result =
sameSeq(a.parents, b.parents)(sameType) &&
sameMembers(a.declarations, b.declarations) &&
sameMembers(a.inherited, b.inherited)
def sameMembers(a: Seq[Definition], b: Seq[Definition]): Result =
sameDefinitions(a, b)
def sameSimpleType(a: SimpleType, b: SimpleType): Result =
(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 (" + a.getClass.getName + " and " + b.getClass.getName + ") for (" + a + " and " + b + ")")
}
def sameParameterized(a: Parameterized, b: Parameterized): Result =
sameSimpleType(a.baseType, b.baseType) &&
2010-01-08 03:48:56 +01:00
sameSeq(a.typeArguments, b.typeArguments)(sameType)
def sameParameterRef(a: ParameterRef, b: ParameterRef): Result =
mapReferences(a.id, b.id)
def mapReferences(a: Int, b: Int) = new True(Map( a -> b ), Map.empty)
def sameSingleton(a: Singleton, b: Singleton): Result =
samePath(a.path, b.path)
def sameProjection(a: Projection, b: Projection): Result =
sameSimpleType(a.prefix, b.prefix) &&
(a.id == b.id)
def samePath(a: Path, b: Path): Result =
samePathComponents(a.components, b.components)
def samePathComponents(a: Seq[PathComponent], b: Seq[PathComponent]): Result =
sameSeq(a, b)(samePathComponent)
def samePathComponent(a: PathComponent, b: PathComponent): Result =
(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): Result =
samePath(a.qualifier, b.qualifier)
def samePathId(a: Id, b: Id): Result =
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]): Result =
a == b
final def sameSeq[T](a: Seq[T], b: Seq[T])(eq: (T,T) => Result): Result =
sameArray(a.toArray, b.toArray)(eq)
final def sameArray[T](a: Array[T], b: Array[T])(eq: (T,T) => Result): Result =
((a.length == b.length): Result) && forall(a.zip(b))(tupled(eq))
final def forall[T](a: Iterable[T])(f: T => Result): Result = forallList(a.toList, true)(f)
final def forallList[T](a: List[T], v: Result)(f: T => Result): Result =
if(v.value) ( a match { case Nil => v; case x :: xs => forallList(xs, v && f(x) )(f) } )
else v
def defaultEquals[T <: AnyRef] = (a: T, b: T) => a == b
}