Merge remote-tracking branch 'gkossakowski/compute-name-hashes' into 0.13

This commit is contained in:
Mark Harrah 2013-12-05 08:53:50 -05:00
commit d8c15bb80e
7 changed files with 481 additions and 32 deletions

View File

@ -12,13 +12,41 @@ object HashAPI
{
type Hash = Int
def apply(a: SourceAPI): Hash =
(new HashAPI(false, true)).hashAPI(a)
(new HashAPI(false, true, true)).hashAPI(a)
def apply(x: Def): Hash = {
val hashApi = new HashAPI(false, true, true)
hashApi.hashDefinition(x)
hashApi.finalizeHash
}
def hashDefinitionsWithExtraHashes(ds: Seq[(Definition, Hash)]): Hash = {
val hashAPI = new HashAPI(false, true, false)
hashAPI.hashDefinitionsWithExtraHashes(ds)
hashAPI.finalizeHash
}
}
final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
/**
* Implements hashing of public API.
*
* @param includePrivate should private definitions be included in a hash sum
* @param includeParamNames should parameter names for methods be included in a hash sum
* @param includeDefinitions when hashing a structure (e.g. of a class) should hashes of definitions (members)
* be included in a hash sum. Structure can appear as a type (in structural type) and in that case we
* always include definitions in a hash sum.
*/
final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean, includeDefinitions: Boolean)
{
// this constructor variant is for source and binary backwards compatibility with sbt 0.13.0
def this(includePrivate: Boolean, includeParamNames: Boolean) {
// in the old logic we used to always include definitions hence
// includeDefinitions=true
this(includePrivate, includeParamNames, includeDefinitions=true)
}
import scala.collection.mutable
import MurmurHash.{extendHash, finalizeHash, nextMagicA, nextMagicB, startHash, startMagicA, startMagicB, stringHash, symmetricHash}
import MurmurHash.{extendHash, nextMagicA, nextMagicB, startHash, startMagicA, startMagicB, stringHash, symmetricHash}
private[this] val visitedStructures = visitedMap[Structure]
private[this] val visitedClassLike = visitedMap[ClassLike]
@ -44,7 +72,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
private[this] final val PublicHash = 30
private[this] final val ProtectedHash = 31
private[this] final val PrivateHash = 32
private[this] final val PrivateHash = 32
private[this] final val UnqualifiedHash = 33
private[this] final val ThisQualifierHash = 34
private[this] final val IdQualifierHash = 35
@ -75,7 +103,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
private[this] var hash: Hash = startHash(0)
private[this] var magicA: Hash = startMagicA
private[this] var magicB: Hash = startMagicB
@inline final def hashString(s: String): Unit = extend(stringHash(s))
@inline final def hashBoolean(b: Boolean): Unit = extend(if(b) TrueHash else FalseHash)
@inline final def hashSeq[T](s: Seq[T], hashF: T => Unit)
@ -93,7 +121,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
magicA = startMagicA
magicB = startMagicB
hashF(t)
(finalizeHash(hash), magicA, magicB)
(finalizeHash, magicA, magicB)
} unzip3;
hash = current
magicA = mA
@ -107,6 +135,9 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
magicA = nextMagicA(magicA)
magicB = nextMagicB(magicB)
}
def finalizeHash: Hash = MurmurHash.finalizeHash(hash)
def hashModifiers(m: Modifiers) = extend(m.raw)
def hashAPI(s: SourceAPI): Hash =
@ -114,7 +145,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
hash = startHash(0)
hashSymmetric(s.packages, hashPackage)
hashDefinitions(s.definitions, true)
finalizeHash(hash)
finalizeHash
}
def hashPackage(p: Package) = hashString(p.name)
@ -124,6 +155,24 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
val defs = SameAPI.filterDefinitions(ds, topLevel, includePrivate)
hashSymmetric(defs, hashDefinition)
}
/**
* Hashes a sequence of definitions by combining each definition's own
* hash with extra one supplied as first element of a pair.
*
* It's useful when one wants to influence hash of a definition by some
* external (to definition) factor (e.g. location of definition).
*
* NOTE: This method doesn't perform any filtering of passed definitions.
*/
def hashDefinitionsWithExtraHashes(ds: Seq[(Definition, Hash)]): Unit =
{
def hashDefinitionCombined(d: Definition, extraHash: Hash): Unit = {
hashDefinition(d)
extend(extraHash)
}
hashSymmetric(ds, (hashDefinitionCombined _).tupled)
}
def hashDefinition(d: Definition)
{
hashString(d.name)
@ -145,7 +194,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
extend(ClassHash)
hashParameterizedDefinition(c)
hashType(c.selfType)
hashStructure(c.structure)
hashStructure(c.structure, includeDefinitions)
}
def hashField(f: FieldLike)
{
@ -202,7 +251,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
extend(parameter.modifier.ordinal)
hashBoolean(parameter.hasDefault)
}
def hashParameterizedDefinition[T <: ParameterizedDefinition](d: T)
{
hashTypeParameters(d.typeParameters)
@ -220,7 +269,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
hashParameterizedDefinition(d)
hashType(d.tpe)
}
def hashTypeParameters(parameters: Seq[TypeParameter]) = hashSeq(parameters, hashTypeParameter)
def hashTypeParameter(parameter: TypeParameter)
{
@ -243,12 +292,13 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
hashString(arg.name)
hashString(arg.value)
}
def hashTypes(ts: Seq[Type]) = hashSeq(ts, hashType)
def hashType(t: Type): Unit =
def hashTypes(ts: Seq[Type], includeDefinitions: Boolean = true) =
hashSeq(ts, (t: Type) => hashType(t, includeDefinitions))
def hashType(t: Type, includeDefinitions: Boolean = true): Unit =
t match
{
case s: Structure => hashStructure(s)
case s: Structure => hashStructure(s, includeDefinitions)
case e: Existential => hashExistential(e)
case c: Constant => hashConstant(c)
case p: Polymorphic => hashPolymorphic(p)
@ -259,7 +309,7 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
case s: Singleton => hashSingleton(s)
case pr: ParameterRef => hashParameterRef(pr)
}
def hashParameterRef(p: ParameterRef)
{
extend(ParameterRefHash)
@ -322,13 +372,16 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
hashType(a.baseType)
hashAnnotations(a.annotations)
}
final def hashStructure(structure: Structure) = visit(visitedStructures, structure)(hashStructure0)
def hashStructure0(structure: Structure)
final def hashStructure(structure: Structure, includeDefinitions: Boolean) =
visit(visitedStructures, structure)(structure => hashStructure0(structure, includeDefinitions))
def hashStructure0(structure: Structure, includeDefinitions: Boolean)
{
extend(StructureHash)
hashTypes(structure.parents)
hashDefinitions(structure.declared, false)
hashDefinitions(structure.inherited, false)
hashTypes(structure.parents, includeDefinitions)
if (includeDefinitions) {
hashDefinitions(structure.declared, false)
hashDefinitions(structure.inherited, false)
}
}
def hashParameters(parameters: Seq[TypeParameter], base: Type): Unit =
{
@ -336,4 +389,4 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
hashType(base)
}
}

View File

@ -7,6 +7,7 @@ package inc
import xsbti.api.Source
import java.io.File
import APIs.getAPI
import xsbti.api._internalOnly_NameHashes
import scala.util.Sorting
import xsbt.api.SameAPI
@ -18,12 +19,12 @@ trait APIs
/** The API for the external class `ext` at the time represented by this instance.
* This method returns an empty API if the file had no API or is not known to this instance. */
def externalAPI(ext: String): Source
def allExternals: collection.Set[String]
def allInternalSources: collection.Set[File]
def ++ (o: APIs): APIs
def markInternalSource(src: File, api: Source): APIs
def markExternalAPI(ext: String, api: Source): APIs
@ -39,10 +40,11 @@ object APIs
{
def apply(internal: Map[File, Source], external: Map[String, Source]): APIs = new MAPIs(internal, external)
def empty: APIs = apply(Map.empty, Map.empty)
val emptyAPI = new xsbti.api.SourceAPI(Array(), Array())
val emptyCompilation = new xsbti.api.Compilation(-1, Array())
val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0, false)
val emptyNameHashes = new xsbti.api._internalOnly_NameHashes(Array.empty, Array.empty)
val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0, emptyNameHashes, false)
def getAPI[T](map: Map[T, Source], src: T): Source = map.getOrElse(src, emptySource)
}
@ -50,15 +52,15 @@ private class MAPIs(val internal: Map[File, Source], val external: Map[String, S
{
def allInternalSources: collection.Set[File] = internal.keySet
def allExternals: collection.Set[String] = external.keySet
def ++ (o: APIs): APIs = new MAPIs(internal ++ o.internal, external ++ o.external)
def markInternalSource(src: File, api: Source): APIs =
new MAPIs(internal.updated(src, api), external)
def markExternalAPI(ext: String, api: Source): APIs =
new MAPIs(internal, external.updated(ext, api))
def removeInternal(remove: Iterable[File]): APIs = new MAPIs(internal -- remove, external)
def filterExt(keep: String => Boolean): APIs = new MAPIs(internal, external.filterKeys(keep))
@deprecated("Broken implementation. OK to remove in 0.14", "0.13.1")

View File

@ -4,11 +4,12 @@
package sbt
package inc
import xsbti.api.{Source, SourceAPI, Compilation, OutputSetting}
import xsbti.api.{Source, SourceAPI, Compilation, OutputSetting, _internalOnly_NameHashes}
import xsbti.compile.{DependencyChanges, Output, SingleOutput, MultipleOutput}
import xsbti.{Position,Problem,Severity}
import Logger.{m2o, problem}
import java.io.File
import xsbti.api.Definition
object IncrementalCompile
{
@ -68,6 +69,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
private[this] val apis = new HashMap[File, (Int, SourceAPI)]
private[this] val usedNames = new HashMap[File, Set[String]]
private[this] val publicNameHashes = new HashMap[File, _internalOnly_NameHashes]
private[this] val unreporteds = new HashMap[File, ListBuffer[Problem]]
private[this] val reporteds = new HashMap[File, ListBuffer[Problem]]
private[this] val binaryDeps = new HashMap[File, Set[File]]
@ -147,6 +149,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
def api(sourceFile: File, source: SourceAPI) {
import xsbt.api.{APIUtil, HashAPI}
if (APIUtil.isScalaSourceName(sourceFile.getName) && APIUtil.hasMacro(source)) macroSources += sourceFile
publicNameHashes(sourceFile) = (new NameHashing).nameHashes(source)
val shouldMinimize = !Incremental.apiDebug(options)
val savedSource = if (shouldMinimize) APIUtil.minimize(source) else source
apis(sourceFile) = (HashAPI(source), savedSource)
@ -165,7 +168,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
val hash = stamp match { case h: Hash => h.value; case _ => new Array[Byte](0) }
// TODO store this in Relations, rather than Source.
val hasMacro: Boolean = macroSources.contains(src)
val s = new xsbti.api.Source(compilation, hash, api._2, api._1, hasMacro)
val s = new xsbti.api.Source(compilation, hash, api._2, api._1, publicNameHashes(src), hasMacro)
val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src))
val direct = sourceDeps.getOrElse(src, Nil: Iterable[File])
val publicInherited = inheritedSourceDeps.getOrElse(src, Nil: Iterable[File])

View File

@ -0,0 +1,143 @@
package sbt.inc
import xsbti.api.SourceAPI
import xsbti.api.Definition
import xsbti.api.DefinitionType
import xsbti.api.ClassLike
import xsbti.api._internalOnly_NameHash
import xsbti.api._internalOnly_NameHashes
import xsbt.api.Visit
/**
* A class that computes hashes for each group of definitions grouped by a simple name.
*
* See `nameHashes` method for details.
*/
class NameHashing {
import NameHashing._
/**
* This method takes an API representation and extracts a flat collection of all
* definitions contained in that API representation. Then it groups definition
* by a simple name. Lastly, it computes a hash sum of all definitions in a single
* group.
*
* NOTE: The hashing sum used for hashing a group of definition is insensitive
* to order of definitions.
*/
def nameHashes(source: SourceAPI): _internalOnly_NameHashes = {
val apiPublicDefs = publicDefs(source)
val (regularDefs, implicitDefs) = apiPublicDefs.partition(locDef => !locDef.definition.modifiers.isImplicit)
val regularNameHashes = nameHashesForLocatedDefinitions(regularDefs)
val implicitNameHashes = nameHashesForLocatedDefinitions(implicitDefs)
new _internalOnly_NameHashes(regularNameHashes.toArray, implicitNameHashes.toArray)
}
private def nameHashesForLocatedDefinitions(locatedDefs: Iterable[LocatedDefinition]): Iterable[_internalOnly_NameHash] = {
val groupedBySimpleName = locatedDefs.groupBy(locatedDef => localName(locatedDef.definition.name))
val hashes = groupedBySimpleName.mapValues(hashLocatedDefinitions)
hashes.toIterable.map({ case (name: String, hash: Int) => new _internalOnly_NameHash(name, hash) })
}
private def hashLocatedDefinitions(locatedDefs: Iterable[LocatedDefinition]): Int = {
val defsWithExtraHashes = locatedDefs.toSeq.map(ld => ld.definition -> ld.location.hashCode)
xsbt.api.HashAPI.hashDefinitionsWithExtraHashes(defsWithExtraHashes)
}
/**
* A visitor that visits given API object and extracts all nested public
* definitions it finds. The extracted definitions have Location attached
* to them which identifies API object's location.
*
* The returned location is basically a path to a definition that contains
* the located definition. For example, if we have:
*
* object Foo {
* class Bar { def abc: Int }
* }
*
* then location of `abc` is Seq((TermName, Foo), (TypeName, Bar))
*/
private class ExtractPublicDefinitions extends Visit {
val locatedDefs = scala.collection.mutable.Buffer[LocatedDefinition]()
private var currentLocation: Location = Location()
override def visitAPI(s: SourceAPI): Unit = {
s.packages foreach visitPackage
s.definitions foreach { case topLevelDef: ClassLike =>
val packageName = {
val fullName = topLevelDef.name()
val lastDotIndex = fullName.lastIndexOf('.')
if (lastDotIndex <= 0) "" else fullName.substring(0, lastDotIndex-1)
}
currentLocation = packageAsLocation(packageName)
visitDefinition(topLevelDef)
}
}
override def visitDefinition(d: Definition): Unit = {
val locatedDef = LocatedDefinition(currentLocation, d)
locatedDefs += locatedDef
d match {
case cl: xsbti.api.ClassLike =>
val savedLocation = currentLocation
currentLocation = classLikeAsLocation(currentLocation, cl)
super.visitDefinition(d)
currentLocation = savedLocation
case _ =>
super.visitDefinition(d)
}
}
}
private def publicDefs(source: SourceAPI): Iterable[LocatedDefinition] = {
val visitor = new ExtractPublicDefinitions
visitor.visitAPI(source)
visitor.locatedDefs
}
private def localName(name: String): String = {
// when there's no dot in name `lastIndexOf` returns -1 so we handle
// that case properly
val index = name.lastIndexOf('.') + 1
name.substring(index)
}
private def packageAsLocation(pkg: String): Location = if (pkg != "") {
val selectors = pkg.split('.').map(name => Selector(name, TermName)).toSeq
Location(selectors: _*)
} else Location.Empty
private def classLikeAsLocation(prefix: Location, cl: ClassLike): Location = {
val selector = {
val clNameType = NameType(cl.definitionType)
Selector(localName(cl.name), clNameType)
}
Location((prefix.selectors :+ selector): _*)
}
}
object NameHashing {
private case class LocatedDefinition(location: Location, definition: Definition)
/**
* Location is expressed as sequence of annotated names. The annotation denotes
* a type of a name, i.e. whether it's a term name or type name.
*
* Using Scala compiler terminology, location is defined as a sequence of member
* selections that uniquely identify a given Symbol.
*/
private case class Location(selectors: Selector*)
private object Location {
val Empty = Location(Seq.empty: _*)
}
private case class Selector(name: String, nameType: NameType)
private sealed trait NameType
private object NameType {
import DefinitionType._
def apply(dt: DefinitionType): NameType = dt match {
case Trait | ClassDef => TypeName
case Module | PackageModule => TermName
}
}
private case object TermName extends NameType
private case object TypeName extends NameType
}

View File

@ -0,0 +1,214 @@
package sbt.inc
import org.junit.runner.RunWith
import xsbti.api._
import xsbt.api.HashAPI
import org.specs2.mutable.Specification
import org.specs2.runner.JUnitRunner
@RunWith(classOf[JUnitRunner])
class NameHashingSpecification extends Specification {
/**
* Very basic test which checks whether a name hash is insensitive to
* definition order (across the whole compilation unit).
*/
"new member" in {
val nameHashing = new NameHashing
val def1 = new Def(Array.empty, strTpe, Array.empty, "foo", publicAccess, defaultModifiers, Array.empty)
val def2 = new Def(Array.empty, intTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
val classBar1 = simpleClass("Bar", def1)
val classBar2 = simpleClass("Bar", def1, def2)
val api1 = new SourceAPI(Array.empty, Array(classBar1))
val api2 = new SourceAPI(Array.empty, Array(classBar2))
val nameHashes1 = nameHashing.nameHashes(api1)
val nameHashes2 = nameHashing.nameHashes(api2)
assertNameHashEqualForRegularName("Bar", nameHashes1, nameHashes2)
assertNameHashEqualForRegularName("foo", nameHashes1, nameHashes2)
nameHashes1.regularMembers.map(_.name).toSeq must not contain("bar")
nameHashes2.regularMembers.map(_.name).toSeq must contain("bar")
}
/**
* Very basic test which checks whether a name hash is insensitive to
* definition order (across the whole compilation unit).
*/
"definition order" in {
val nameHashing = new NameHashing
val def1 = new Def(Array.empty, intTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
val def2 = new Def(Array.empty, strTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
val nestedBar1 = simpleClass("Bar1", def1)
val nestedBar2 = simpleClass("Bar2", def2)
val classA = simpleClass("Foo", nestedBar1, nestedBar2)
val classB = simpleClass("Foo", nestedBar2, nestedBar1)
val api1 = new SourceAPI(Array.empty, Array(classA))
val api2 = new SourceAPI(Array.empty, Array(classB))
val nameHashes1 = nameHashing.nameHashes(api1)
val nameHashes2 = nameHashing.nameHashes(api2)
val def1Hash = HashAPI(def1)
val def2Hash = HashAPI(def2)
def1Hash !=== def2Hash
nameHashes1 === nameHashes2
}
/**
* Very basic test which asserts that a name hash is sensitive to definition location.
*
* For example, if we have:
* // Foo1.scala
* class Foo { def xyz: Int = ... }
* object Foo
*
* and:
* // Foo2.scala
* class Foo
* object Foo { def xyz: Int = ... }
*
* then hash for `xyz` name should differ in those two cases
* because method `xyz` was moved from class to an object.
*/
"definition location" in {
val nameHashing = new NameHashing
val deff = new Def(Array.empty, intTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
val classA = {
val nestedBar1 = simpleClass("Bar1", deff)
val nestedBar2 = simpleClass("Bar2")
simpleClass("Foo", nestedBar1, nestedBar2)
}
val classB = {
val nestedBar1 = simpleClass("Bar1")
val nestedBar2 = simpleClass("Bar2", deff)
simpleClass("Foo", nestedBar1, nestedBar2)
}
val api1 = new SourceAPI(Array.empty, Array(classA))
val api2 = new SourceAPI(Array.empty, Array(classB))
val nameHashes1 = nameHashing.nameHashes(api1)
val nameHashes2 = nameHashing.nameHashes(api2)
nameHashes1 !=== nameHashes2
}
/**
* Test if members introduced in parent class affect hash of a name
* of a child class.
*
* For example, if we have:
* // Test1.scala
* class Parent
* class Child extends Parent
*
* and:
* // Test2.scala
* class Parent { def bar: Int = ... }
* class Child extends Parent
*
* then hash for `Child` name should be the same in both
* cases.
*/
"definition in parent class" in {
val parentA = simpleClass("Parent")
val barMethod = new Def(Array.empty, intTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
val parentB = simpleClass("Parent", barMethod)
val childA = {
val structure = new Structure(lzy(Array[Type](parentA.structure)), lzy(Array.empty[Definition]), lzy(Array.empty[Definition]))
simpleClass("Child", structure)
}
val childB = {
val structure = new Structure(lzy(Array[Type](parentB.structure)), lzy(Array.empty[Definition]), lzy(Array[Definition](barMethod)))
simpleClass("Child", structure)
}
val parentANameHashes = nameHashesForClass(parentA)
val parentBNameHashes = nameHashesForClass(parentB)
Seq("Parent") === parentANameHashes.regularMembers.map(_.name).toSeq
Seq("Parent", "bar") === parentBNameHashes.regularMembers.map(_.name).toSeq
parentANameHashes !=== parentBNameHashes
val childANameHashes = nameHashesForClass(childA)
val childBNameHashes = nameHashesForClass(childB)
assertNameHashEqualForRegularName("Child", childANameHashes, childBNameHashes)
}
/**
* Checks if changes to structural types that appear in method signature
* affect name hash of the method. For example, if we have:
*
* // Test1.scala
* class A {
* def foo: { bar: Int }
* }
*
* // Test2.scala
* class A {
* def foo: { bar: String }
* }
*
* then name hash for "foo" should be different in those two cases.
*/
"structural type in definition" in {
/** def foo: { bar: Int } */
val fooMethod1 = {
val barMethod1 = new Def(Array.empty, intTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
new Def(Array.empty, simpleStructure(barMethod1), Array.empty, "foo", publicAccess, defaultModifiers, Array.empty)
}
/** def foo: { bar: String } */
val fooMethod2 = {
val barMethod2 = new Def(Array.empty, strTpe, Array.empty, "bar", publicAccess, defaultModifiers, Array.empty)
new Def(Array.empty, simpleStructure(barMethod2), Array.empty, "foo", publicAccess, defaultModifiers, Array.empty)
}
val aClass1 = simpleClass("A", fooMethod1)
val aClass2 = simpleClass("A", fooMethod2)
val nameHashes1 = nameHashesForClass(aClass1)
val nameHashes2 = nameHashesForClass(aClass2)
// note that `bar` does appear here
Seq("A", "foo", "bar") === nameHashes1.regularMembers.map(_.name).toSeq
Seq("A", "foo", "bar") === nameHashes2.regularMembers.map(_.name).toSeq
assertNameHashEqualForRegularName("A", nameHashes1, nameHashes2)
assertNameHashNotEqualForRegularName("foo", nameHashes1, nameHashes2)
assertNameHashNotEqualForRegularName("bar", nameHashes1, nameHashes2)
}
private def assertNameHashEqualForRegularName(name: String, nameHashes1: _internalOnly_NameHashes,
nameHashes2: _internalOnly_NameHashes): Unit = {
val nameHash1 = nameHashForRegularName(nameHashes1, name)
val nameHash2 = nameHashForRegularName(nameHashes1, name)
nameHash1 === nameHash2
}
private def assertNameHashNotEqualForRegularName(name: String, nameHashes1: _internalOnly_NameHashes,
nameHashes2: _internalOnly_NameHashes): Unit = {
val nameHash1 = nameHashForRegularName(nameHashes1, name)
val nameHash2 = nameHashForRegularName(nameHashes2, name)
nameHash1 !=== nameHash2
}
private def nameHashForRegularName(nameHashes: _internalOnly_NameHashes, name: String): _internalOnly_NameHash =
try {
nameHashes.regularMembers.find(_.name == name).get
} catch {
case e: NoSuchElementException => throw new RuntimeException(s"Couldn't find $name in $nameHashes", e)
}
private def nameHashesForClass(cl: ClassLike): _internalOnly_NameHashes = {
val sourceAPI = new SourceAPI(Array.empty, Array(cl))
val nameHashing = new NameHashing
nameHashing.nameHashes(sourceAPI)
}
private def lzy[T](x: T): Lazy[T] = new Lazy[T] { def get: T = x }
private def simpleStructure(defs: Definition*) = new Structure(lzy(Array.empty[Type]), lzy(defs.toArray), lzy(Array.empty[Definition]))
private def simpleClass(name: String, defs: Definition*): ClassLike = {
val structure = simpleStructure(defs: _*)
simpleClass(name, structure)
}
private def simpleClass(name: String, structure: Structure): ClassLike = {
new ClassLike(DefinitionType.ClassDef, lzy(emptyType), lzy(structure), Array.empty, Array.empty, name, publicAccess, defaultModifiers, Array.empty)
}
private val emptyType = new EmptyType
private val intTpe = new Projection(emptyType, "Int")
private val strTpe = new Projection(emptyType, "String")
private val publicAccess = new Public
private val defaultModifiers = new Modifiers(false, false, false, false, false, false, false)
}

View File

@ -71,13 +71,38 @@ object TestCaseGenerators {
private [this] def lzy[T <: AnyRef](x: T) = SafeLazy.strict(x)
def genNameHash(defn: String): Gen[xsbti.api._internalOnly_NameHash] =
value(new xsbti.api._internalOnly_NameHash(defn, defn.hashCode()))
def genNameHashes(defns: Seq[String]): Gen[xsbti.api._internalOnly_NameHashes] = {
def partitionAccordingToMask[T](mask: List[Boolean], xs: List[T]): (List[T], List[T]) = {
val (p1, p2) = (mask zip xs).partition(_._1)
(p1.map(_._2), p2.map(_._2))
}
val pairsOfGenerators = for (defn <- defns) yield {
for {
isRegularMember <- arbitrary[Boolean]
nameHash <- genNameHash(defn)
} yield (isRegularMember, nameHash)
}
val genNameHashesList = Gen.sequence[List, xsbti.api._internalOnly_NameHash](defns.map(genNameHash))
val genTwoListOfNameHashes = for {
nameHashesList <- genNameHashesList
isRegularMemberList <- listOfN(nameHashesList.length, arbitrary[Boolean])
} yield partitionAccordingToMask(isRegularMemberList, nameHashesList)
for {
(regularMemberNameHashes, implicitMemberNameHashes) <- genTwoListOfNameHashes
} yield new xsbti.api._internalOnly_NameHashes(regularMemberNameHashes.toArray, implicitMemberNameHashes.toArray)
}
def genSource(defns: Seq[String]): Gen[Source] = for {
startTime <- arbitrary[Long]
hashLen <- choose(10, 20) // Requred by SameAPI to be > 0.
hash <- Gen.containerOfN[Array,Byte](hashLen, arbitrary[Byte])
apiHash <- arbitrary[Int]
hasMacro <- arbitrary[Boolean]
} yield new Source(new Compilation(startTime, Array()), hash, new SourceAPI(Array(), Array(defns map makeDefinition:_*)), apiHash, hasMacro)
nameHashes <- genNameHashes(defns)
} yield new Source(new Compilation(startTime, Array()), hash, new SourceAPI(Array(), Array(defns map makeDefinition:_*)), apiHash, nameHashes, hasMacro)
def genSources(all_defns: Seq[Seq[String]]): Gen[Seq[Source]] = Gen.sequence[List, Source](all_defns.map(genSource))

View File

@ -3,8 +3,17 @@ Source
hash: Byte*
api: SourceAPI
apiHash: Int
_internalOnly_nameHashes: _internalOnly_NameHashes
hasMacro: Boolean
_internalOnly_NameHashes
regularMembers: _internalOnly_NameHash*
implicitMembers: _internalOnly_NameHash*
_internalOnly_NameHash
name: String
hash: Int
SourceAPI
packages : Package*
definitions: Definition*