From 80780d9bec77d71e1195717ba5ccf489f15bbbee Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 24 Jan 2010 00:12:47 -0500 Subject: [PATCH] comments and reorganization in SameAPI/ShowAPI --- compile/api/SameAPI.scala | 107 +++++++++++++++++++++++++------------- compile/api/ShowAPI.scala | 11 ++-- 2 files changed, 77 insertions(+), 41 deletions(-) diff --git a/compile/api/SameAPI.scala b/compile/api/SameAPI.scala index d944f4329..04b842ed3 100644 --- a/compile/api/SameAPI.scala +++ b/compile/api/SameAPI.scala @@ -8,6 +8,27 @@ import xsbti.api._ import Function.tupled import scala.collection.{immutable, mutable} +object TopLevel +{ + class Changes(val newTypeNames: Set[String], val removedTypeNames: Set[String], val newValueNames: Set[String], val removedValueNames: Set[String]) + /** Identifies removed and new top-level definitions by name. */ + def nameChanges(a: Iterable[Source], b: Iterable[Source]): Changes = + { + def definitions(i: Iterable[Source]) = SameAPI.separateDefinitions(i.toSeq.flatMap( _.definitions )) + def names(s: Iterable[Definition]): Set[String] = Set() ++ s.map(_.name) + def changes(s: Set[String], t: Set[String]) = (s -- t, t -- s) + + val (avalues, atypes) = definitions(a) + val (bvalues, btypes) = definitions(b) + + val (newTypes, removedTypes) = changes(names(atypes), names(btypes)) + val (newValues, removedValues) = changes(names(avalues), names(bvalues)) + + new Changes(newTypes, removedTypes, newValues, removedValues) + } +} + +/** Checks the API of two source files for equality.*/ object SameAPI { def apply(a: Source, b: Source) = @@ -20,24 +41,54 @@ object SameAPI println("\n=========== API #2 ================") println(ShowAPI.show(b)) - val result = (new SameAPI(a,b)).check + val result = (new SameAPI(a,b, false)).check val end = System.currentTimeMillis println(" API comparison took: " + (end - start) / 1000.0 + " s") result } + + def separateDefinitions(s: Seq[Definition]): (Seq[Definition], Seq[Definition]) = + s.toArray.partition(isValueDefinition) + 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 + /** Puts the given definitions in a map according to their names.*/ + 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 + } } -private class SameAPI(a: Source, b: Source) +/** Used to implement API equality. All comparisons must be done between constructs in source files `a` and `b`. For example, when doing: +* `sameDefinitions(as, bs)`, `as` must be definitions from source file `a` and `bs` must be definitions from source file `b`. This is in order +* to properly handle type parameters, which must be computed for each source file and then referenced during comparison. +* +* If `includePrivate` is true, `private` and `private[this]` members are included in the comparison. Otherwise, those members are excluded. +*/ +private class SameAPI(a: Source, b: Source, includePrivate: Boolean) { + import SameAPI._ + /** de Bruijn levels for type parameters in source `a`*/ private lazy val tagsA = TagTypeVariables(a) + /** de Bruijn levels for type parameters in source `a`*/ private lazy val tagsB = TagTypeVariables(b) def debug(flag: Boolean, msg: => String): Boolean = { - if(!flag) println(msg) + //if(!flag) println(msg) flag } - lazy val check: Boolean = + /** Returns true if source `a` has the same API as source `b`.*/ + def check: Boolean = { samePackages(a, b) && debug(sameDefinitions(a, b), "Definitions differed") @@ -49,32 +100,34 @@ private class SameAPI(a: Source, b: Source) 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 = + sameDefinitions(a.definitions, b.definitions, true) + def sameDefinitions(a: Seq[Definition], b: Seq[Definition], topLevel: Boolean): Boolean = { - val (avalues, atypes) = separateDefinitions(filterDefinitions(a)) - val (bvalues, btypes) = separateDefinitions(filterDefinitions(b)) + val (avalues, atypes) = separateDefinitions(filterDefinitions(a, topLevel)) + val (bvalues, btypes) = separateDefinitions(filterDefinitions(b, topLevel)) 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 filterDefinitions(d: Seq[Definition]) = d.filter(isNonPrivate) + /** Removes definitions that should not be considered for API equality. + * All top-level definitions are always considered: 'private' only means package-private. + * Other definitions are considered if they are not qualified with 'private[this]' or 'private'.*/ + def filterDefinitions(d: Seq[Definition], topLevel: Boolean) = if(topLevel || includePrivate) d else d.filter(isNonPrivate) def isNonPrivate(d: Definition): Boolean = isNonPrivate(d.access) - def isNonPrivate(a: Access): Boolean = - a match + /** Returns false if the `access` is `Private` and qualified, true otherwise.*/ + def isNonPrivate(access: Access): Boolean = + access match { - case p: Private if p.qualifier.isInstanceOf[IdQualifier] => false + case p: Private if !p.qualifier.isInstanceOf[IdQualifier] => false case _ => true } + /** Checks that the definitions in `a` are the same as those in `b`, ignoring order. + * Each list is assumed to have already been checked to have the same names (by `sameDefinitions`, for example).*/ def sameNamedDefinitions(a: List[Definition], b: List[Definition]): Boolean = { - def show(x: List[Definition]) = x.map(DefaultShowAPI.apply).mkString("{\n\t", "\n\t", "\n}\n") - //println("Comparing \n\t" + show(a) + "\nagainst\n\t" + show(b)) def sameDefs(a: List[Definition], b: List[Definition]): Boolean = { a match @@ -92,28 +145,10 @@ private class SameAPI(a: Source, b: Source) case Nil => true } } - //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) } - 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 + /** Checks that the two definitions are the same, other than their name.*/ def sameDefinitionContent(a: Definition, b: Definition): Boolean = //a.name == b.name && debug(sameAccess(a.access, b.access), "Access differed") && @@ -276,7 +311,7 @@ private class SameAPI(a: Source, b: Source) sameMembers(a.inherited, b.inherited) def sameMembers(a: Seq[Definition], b: Seq[Definition]): Boolean = - sameDefinitions(a, b) + sameDefinitions(a, b, false) def sameSimpleType(a: SimpleType, b: SimpleType): Boolean = (a, b) match diff --git a/compile/api/ShowAPI.scala b/compile/api/ShowAPI.scala index 405e86cbb..82df7b85a 100644 --- a/compile/api/ShowAPI.scala +++ b/compile/api/ShowAPI.scala @@ -38,7 +38,7 @@ object ShowAPI def concat[A](list: Seq[A], as: Show[A], sep: String): String = mapSeq(list, as).mkString(sep) def commas[A](list: Seq[A], as: Show[A]): String = concat(list, as, ", ") def spaced[A](list: Seq[A], as: Show[A]): String = concat(list, as, " ") - def lines[A](list: Seq[A], as: Show[A]): String = mapSeq(list, as).mkString("", "\n", "\n") + def lines[A](list: Seq[A], as: Show[A]): String = mapSeq(list, as).mkString("\n") def mapSeq[A](list: Seq[A], as: Show[A]): Seq[String] = list.map(as.show) } @@ -55,7 +55,7 @@ trait ShowBase new Show[Variance] { def show(v: Variance) = v match { case Invariant => ""; case Covariant => "+"; case Contravariant => "-" } } implicit def showSource(implicit ps: Show[Package], ds: Show[Definition]): Show[Source] = - new Show[Source] { def show(a: Source) = lines(a.packages, ps) + lines(a.definitions, ds) } + new Show[Source] { def show(a: Source) = lines(a.packages, ps) + "\n" + lines(a.definitions, ds) } implicit def showPackage: Show[Package] = new Show[Package] { def show(pkg: Package) = "package " + pkg.name } @@ -66,7 +66,7 @@ trait ShowBase def show(a: Access) = a match { - case p: Public => "public" + case p: Public => "" case q: Qualified => sq.show(q) } } @@ -147,7 +147,8 @@ trait ShowDefinitions def parameterizedDef(d: ParameterizedDefinition, label: String)(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]]): String = definitionBase(d, label)(acs, ms, ans) + tp.show(d.typeParameters) def definitionBase(d: Definition, label: String)(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation]): String = - spaced(d.annotations, ans) + " " + acs.show(d.access) + " " + ms.show(d.modifiers) + " " + label + " " + d.name + space(spaced(d.annotations, ans)) + space(acs.show(d.access)) + space(ms.show(d.modifiers)) + space(label) + d.name + def space(s: String) = if(s.isEmpty) s else s + " " } trait ShowDefinition { @@ -209,7 +210,7 @@ trait ShowTypes implicit def showStructure(implicit t: Show[Type], d: Show[Definition]): Show[Structure] = new Show[Structure] { def show(s: Structure) = - concat(s.parents, t, " with ") + "\n{\n\tDeclared:" + lines(s.declared, d) + "\n\tInherited:" + lines(s.inherited, d) + "\n}" + concat(s.parents, t, " with ") + "\n{\n Declared:\n" + lines(s.declared, d) + "\n Inherited:\n" + lines(s.inherited, d) + "\n}" } implicit def showAnnotated(implicit as: Show[Annotation], t: Show[SimpleType]): Show[Annotated] = new Show[Annotated] { def show(a: Annotated) = spaced(a.annotations, as) + " " + t.show(a.baseType) }