Add hashing of public names defined in a source file.

A hash for given name in a source file is computed by combining
hashes of all definitions with given name. When hashing a single
definition we take into account all information about it except nested
definitions. For example, if we have following definition

class Foo[T] {
  def bar(x: Int): Int = ???
}

hash sum for `Foo` will include the fact that we have a class with
a single type parameter but it won't include hash sum of `bar` method.

Computed hash sums are location-sensitive. Each definition is hashed along
with its location so we properly detect cases when definition's signature
stays the same but it's moved around in the same compilation unit.

The location is defined as sequence of selections. Each selection consists
of a name and name type. The name type is either term name or type name.
Scala specification (9.2) guarantees that each publicly visible definition
is uniquely identified by a sequence of such selectors.

For example, if we have:

object Foo {
  class Bar { def abc: Int }
}

then location of `abc` is Seq((TermName, Foo), (TypeName, Bar))

It's worth mentioning that we track name-hash pairs separately for
regular (non implicit) and implicit members. That's required for name
hashing algorithm because it does not apply its heuristic when implicit
members are being modified.

Another important characteristic is that we include all inherited members
when computing name hashes.

Here comes the detailed list of changes made in this commit:

  * HashAPI has new parameter `includeDefinitions` that allows
    shallow hashing of Structures (where we do not compute hashes
    recursively)
  * HashAPI exposes `finalizeHash` method that allow one to capture
    current hash at any time. This is useful if you want to hash a list of
    definitions and not just whole `SourceAPI`.
  * NameHashing implements actual extraction of public definitions,
    grouping them by simple name and computing hash sums for each group
    using HashAPI
  * `Source` class (defined in interface/other file) has been extended to
    include `_internalOnly_nameHashes` field. This field stores
    NameHashes data structure for given source file. The NameHashes
    stores two separate collections of name-hash pairs for regular and
    implicit members.
    The prefix `_internalOnly_` is used to indicate that this is not an
    official incremental compiler's or sbt's API and it's for use by
    incremental compiler internals only. We had to use such a prefix
    because the `datatype` code generator doesn't support emitting access
    modifiers
  * `AnalysisCallback` implementation has been modified to gather all
    name hashes and store them in the Source object
  * TestCaseGenerators has been modified to implement generation of
    NameHashes
  * The NameHashingSpecification contains a few unit tests that make sure
    that the basic functionality works properly
This commit is contained in:
Grzegorz Kossakowski 2013-12-04 01:34:18 +01:00
parent ec40eab92d
commit a9a709ccc0
7 changed files with 473 additions and 31 deletions

View File

@ -12,10 +12,31 @@ 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)
{
import scala.collection.mutable
import MurmurHash.{extendHash, finalizeHash, nextMagicA, nextMagicB, startHash, startMagicA, startMagicB, stringHash, symmetricHash}
@ -44,7 +65,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 +96,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 +114,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 +128,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 +138,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 +148,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 +187,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 +244,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 +262,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 +285,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 +302,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 +365,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 +382,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*