mirror of https://github.com/sbt/sbt.git
Merge remote-tracking branch 'gkossakowski/compute-name-hashes' into 0.13
This commit is contained in:
commit
d8c15bb80e
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
|
|
|||
Loading…
Reference in New Issue