approximate type parameters and references by name

not as accurate, but simpler.
This commit is contained in:
Mark Harrah 2012-05-12 23:12:29 -04:00
parent 00d0918714
commit 864580aae1
9 changed files with 21 additions and 233 deletions

View File

@ -16,34 +16,6 @@ object APIUtil
new Modifiers( x(0), x(1), x(2), x(3), x(4), x(5), x(6) )
}
def verifyTypeParameters(s: SourceAPI): Boolean =
{
val check = new CheckTypeParameters
val invalid = check(s)
if(!invalid.isEmpty) println("References to undefined type parameters: " + invalid.mkString(", "))
invalid.isEmpty
}
private[this] class CheckTypeParameters extends Visit
{
private val defined = new HashSet[Int]
private val referenced = new HashSet[Int]
def apply(s: SourceAPI): List[Int] =
{
super.visitAPI(s)
(referenced filterNot defined).toList
}
override def visitTypeParameter(parameter: TypeParameter)
{
defined += parameter.id
super.visitTypeParameter(parameter)
}
override def visitParameterRef(ref: ParameterRef)
{
referenced += ref.id
super.visitParameterRef(ref)
}
}
def hasMacro(s: SourceAPI): Boolean =
{
val check = new HasMacro

View File

@ -172,9 +172,8 @@ object ClassToAPI
new api.TypeParameter(typeVariable(tp), emptyAnnotationArray, emptyTypeParameterArray, api.Variance.Invariant, NothingRef, upperBounds(tp.getBounds))
// needs to be stable across compilations
// preferably, it would be a proper unique id based on de Bruijn index
def typeVariable[T <: GenericDeclaration](tv: TypeVariable[T]): Int =
reduceHash((name(tv.getGenericDeclaration) + " " + tv.getName).getBytes)
def typeVariable[T <: GenericDeclaration](tv: TypeVariable[T]): String =
name(tv.getGenericDeclaration) + " " + tv.getName
def reduceHash(in: Array[Byte]): Int =
(0 /: in)( (acc, b) => (acc * 43) ^ b)

View File

@ -6,21 +6,16 @@ package xsbt.api
import scala.util
import xsbti.api._
import util.MurmurHash
import TagTypeVariables.TypeVars
import HashAPI.Hash
object HashAPI
{
type Hash = Int
def apply(a: SourceAPI): Hash =
{
/** de Bruijn levels for type parameters in source a and b*/
val tags = TagTypeVariables(a)
(new HashAPI(tags, false, true)).hashAPI(a)
}
(new HashAPI(false, true)).hashAPI(a)
}
final class HashAPI(tags: TypeVars, includePrivate: Boolean, includeParamNames: Boolean)
final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean)
{
import scala.collection.mutable
import MurmurHash._
@ -229,6 +224,7 @@ final class HashAPI(tags: TypeVars, includePrivate: Boolean, includeParamNames:
def hashTypeParameters(parameters: Seq[TypeParameter]) = hashSeq(parameters, hashTypeParameter)
def hashTypeParameter(parameter: TypeParameter)
{
hashString(parameter.id)
extend(parameter.variance.ordinal)
hashTypeParameters(parameter.typeParameters)
hashType(parameter.lowerBound)
@ -267,10 +263,7 @@ final class HashAPI(tags: TypeVars, includePrivate: Boolean, includeParamNames:
def hashParameterRef(p: ParameterRef)
{
extend(ParameterRefHash)
tags.get(p.id) match {
case Some((a,b)) => extend(a); extend(b)
case None => extend(-1)
}
hashString(p.id)
}
def hashSingleton(s: Singleton)
{

View File

@ -39,7 +39,7 @@ object TopLevel
def definitions(i: Iterable[SourceAPI]) = SameAPI.separateDefinitions(i.toSeq.flatMap( _.definitions ))
def names(s: Iterable[Definition]): Set[String] = Set() ++ s.map(_.name)
}
import TagTypeVariables.TypeVars
/** Checks the API of two source files for equality.*/
object SameAPI
{
@ -56,11 +56,7 @@ object SameAPI
println("\n=========== API #2 ================")
println(ShowAPI.show(b))*/
/** de Bruijn levels for type parameters in source a and b*/
val tagsA = TagTypeVariables(a)
val tagsB = TagTypeVariables(b)
val result = (new SameAPI(tagsA,tagsB, false, true)).check(a,b)
val result = (new SameAPI(false, true)).check(a,b)
val end = System.currentTimeMillis
//println(" API comparison took: " + (end - start) / 1000.0 + " s")
result
@ -99,13 +95,11 @@ object SameAPI
case _ => true
}
}
/** 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.
/** Used to implement API equality.
*
* If `includePrivate` is true, `private` and `private[this]` members are included in the comparison. Otherwise, those members are excluded.
*/
class SameAPI(tagsA: TypeVars, tagsB: TypeVars, includePrivate: Boolean, includeParamNames: Boolean)
class SameAPI(includePrivate: Boolean, includeParamNames: Boolean)
{
import SameAPI._
@ -304,13 +298,8 @@ class SameAPI(tagsA: TypeVars, tagsB: TypeVars, includePrivate: Boolean, include
debug(sameType(a.upperBound, b.upperBound), "Different upper bound") &&
sameTags(a.id, b.id)
}
// until dangling type parameter references are straightened out in API phase, approximate
def sameTags(a: Int, b: Int): Boolean =
{
val ta = tagsA.get(a)
val tb = tagsB.get(b)
debug(ta == tb, "Different type parameter bindings: " + ta + "(" + a + "), " + tb + "(" + b + ")")
}
def sameTags(a: String, b: String): Boolean =
debug(a == b, "Different type parameter bindings: " + a + ", " + b)
def sameType(a: Type, b: Type): Boolean =
samePending(a,b)(sameTypeDirect)

View File

@ -1,164 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package xsbt.api
import xsbti.api._
import scala.collection.mutable
object TagTypeVariables
{
type TypeVars = collection.Map[Int, (Int, Int)]
def apply(s: SourceAPI): TypeVars = (new TagTypeVariables).tag(s)
}
import TagTypeVariables.TypeVars
private class TagTypeVariables
{
private val taggedStructures = new mutable.HashSet[Structure]
private val taggedClasses = new mutable.HashSet[ClassLike]
private val tags = new mutable.HashMap[Int, (Int, Int)]
private var level = 0
private var index = 0
def tag(s: SourceAPI): TypeVars =
{
tagDefinitions(s.definitions)
tags
}
def tagDefinitions(ds: Seq[Definition], eachFresh: Boolean = false): Unit =
for(d <- ds) if(eachFresh) freshScope(tagDefinition(d)) else tagDefinition(d)
def tagDefinition(d: Definition)
{
d match
{
case c: ClassLike => tagClass(c)
case f: FieldLike => tagField(f)
case d: Def => tagDef(d)
case t: TypeDeclaration => tagTypeDeclaration(t)
case t: TypeAlias => tagTypeAlias(t)
}
}
def tagClass(c: ClassLike): Unit = if(taggedClasses add c) tagClass0(c)
def tagClass0(c: ClassLike): Unit =
tagParameterizedDefinition(c) {
tagType(c.selfType)
tagStructure(c.structure)
}
def tagField(f: FieldLike)
{
tagType(f.tpe)
tagAnnotations(f.annotations)
}
def tagDef(d: Def): Unit =
tagParameterizedDefinition(d) {
tagValueParameters(d.valueParameters)
tagType(d.returnType)
}
def tagValueParameters(valueParameters: Seq[ParameterList]) = valueParameters.foreach(tagValueParameterList)
def tagValueParameterList(list: ParameterList) = list.parameters.foreach(tagValueParameter)
def tagValueParameter(parameter: MethodParameter) = tagType(parameter.tpe)
def tagParameterizedDefinition[T <: ParameterizedDefinition](d: T)(tagExtra: => Unit)
{
tagAnnotations(d.annotations)
scope {
tagTypeParameters(d.typeParameters)
tagExtra
}
}
def tagTypeDeclaration(d: TypeDeclaration): Unit =
tagParameterizedDefinition(d) {
tagType(d.lowerBound)
tagType(d.upperBound)
}
def tagTypeAlias(d: TypeAlias): Unit =
tagParameterizedDefinition(d) {
tagType(d.tpe)
}
def tagTypeParameters(parameters: Seq[TypeParameter]) = parameters.foreach(tagTypeParameter)
def tagTypeParameter(parameter: TypeParameter)
{
recordTypeParameter(parameter.id)
scope {
tagTypeParameters(parameter.typeParameters)
tagType(parameter.lowerBound)
tagType(parameter.upperBound)
}
}
def tagAnnotations(annotations: Seq[Annotation]) = tagTypes(annotations.map(_.base))
def tagTypes(ts: Seq[Type]) = ts.foreach(tagType)
def tagType(t: Type)
{
t match
{
case s: Structure => tagStructure(s)
case e: Existential => tagExistential(e)
case c: Constant => tagConstant(c)
case p: Polymorphic => tagPolymorphic(p)
case a: Annotated => tagAnnotated(a)
case p: Parameterized => tagParameterized(p)
case p: Projection => tagProjection(p)
case _: EmptyType | _: Singleton | _: ParameterRef => ()
}
}
def tagConstant(c: Constant) = tagType(c.baseType)
def tagExistential(e: Existential) = tagParameters(e.clause, e.baseType)
def tagPolymorphic(p: Polymorphic) = tagParameters(p.parameters, p.baseType)
def tagProjection(p: Projection) = tagType(p.prefix)
def tagParameterized(p: Parameterized)
{
tagType(p.baseType)
tagTypes(p.typeArguments)
}
def tagAnnotated(a: Annotated)
{
tagType(a.baseType)
tagAnnotations(a.annotations)
}
def tagStructure(structure: Structure): Unit = if(taggedStructures add structure) tagStructure0(structure)
def tagStructure0(structure: Structure)
{
tagTypes(structure.parents)
tagDefinitions(structure.declared)
tagDefinitions(structure.inherited, eachFresh=true)
}
def tagParameters(parameters: Seq[TypeParameter], base: Type): Unit =
scope {
tagTypeParameters(parameters)
tagType(base)
}
def scope(action: => Unit)
{
val saveIndex = index
index = 0
level += 1
action
level -= 1
index = saveIndex
}
def freshScope(action: => Unit)
{
val saveIndex = index
val saveLevel = level
index = 0
level = 0
action
level = saveLevel
index = saveIndex
}
def recordTypeParameter(id: Int)
{
tags(id) = (level, index)
index += 1
}
}

View File

@ -126,9 +126,7 @@ final class API(val global: CallbackGlobal) extends Compat
else if(sym.isRoot || sym.isRootPackage) Constants.emptyType
else new xsbti.api.Projection(simpleType(in, pre), sym.nameString)
}
private def reference(sym: Symbol): xsbti.api.ParameterRef = new xsbti.api.ParameterRef(sym.id)
private def reference(sym: Symbol): xsbti.api.ParameterRef = new xsbti.api.ParameterRef(tparamID(sym))
private def annotations(in: Symbol, as: List[AnnotationInfo]): Array[xsbti.api.Annotation] = as.toArray[AnnotationInfo].map(annotation(in,_))
private def annotation(in: Symbol, a: AnnotationInfo) =
@ -338,11 +336,12 @@ final class API(val global: CallbackGlobal) extends Compat
val variance = if(varianceInt < 0) Contravariant else if(varianceInt > 0) Covariant else Invariant
viewer(in).memberInfo(s) match
{
case TypeBounds(low, high) => new xsbti.api.TypeParameter( s.id, annots, typeParameters(in, s), variance, processType(in, low), processType(in, high) )
case PolyType(typeParams, base) => new xsbti.api.TypeParameter( s.id, annots, typeParameters(in, typeParams), variance, processType(in, base.bounds.lo), processType(in, base.bounds.hi))
case TypeBounds(low, high) => new xsbti.api.TypeParameter( tparamID(s), annots, typeParameters(in, s), variance, processType(in, low), processType(in, high) )
case PolyType(typeParams, base) => new xsbti.api.TypeParameter( tparamID(s), annots, typeParameters(in, typeParams), variance, processType(in, base.bounds.lo), processType(in, base.bounds.hi))
case x => error("Unknown type parameter info: " + x.getClass)
}
}
private def tparamID(s: Symbol) = s.fullName
private def selfType(in: Symbol, s: Symbol): xsbti.api.Type = processType(in, s.thisSym.typeOfThis)
private def classLike(in: Symbol, c: Symbol): ClassLike = classLikeCache.getOrElseUpdate( (in,c), mkClassLike(in, c))

View File

@ -62,8 +62,8 @@ trait APIFormats extends FormatExtra
implicit def formatExistential(implicit t: Format[Type], tps: Format[Array[TypeParameter]]): Format[Existential] =
p2( (e: Existential) => (e.baseType, e.clause) )( new Existential(_,_) )(t,tps)
implicit def formatParameterRef(implicit i: Format[Int]): Format[ParameterRef] =
wrap[ParameterRef, Int](_.id, new ParameterRef(_))(i)
implicit def formatParameterRef(implicit i: Format[String]): Format[ParameterRef] =
wrap[ParameterRef, String](_.id, new ParameterRef(_))(i)
// cyclic with many formats
def formatType(implicit s: Format[SimpleType], a: Format[Annotated], st: Format[Structure], e: Format[Existential], po: Format[Polymorphic]): Format[Type] =
@ -172,7 +172,7 @@ trait APIFormats extends FormatExtra
implicit def formatModifiers(implicit bf: Format[Byte]): Format[Modifiers] =
wrap[Modifiers, Byte]( modifiersToByte, byteToModifiers )
def formatTypeParameter(tps: Format[TypeParameter] => Format[Array[TypeParameter]])(implicit as: Format[Array[Annotation]], t: Format[Type], v: Format[Variance], i: Format[Int]): Format[TypeParameter] =
def formatTypeParameter(tps: Format[TypeParameter] => Format[Array[TypeParameter]])(implicit as: Format[Array[Annotation]], t: Format[Type], v: Format[Variance], i: Format[String]): Format[TypeParameter] =
{
lazy val ltps: Format[Array[TypeParameter]] = lazyFormat( tps(ltp) )
lazy val ltp = p6( (tp: TypeParameter) => (tp.id, tp.annotations, tp.typeParameters, tp.variance, tp.lowerBound, tp.upperBound))(new TypeParameter(_,_,_,_,_,_))(i, as, ltps, v, t, t)

View File

@ -39,7 +39,7 @@ MethodParameter
modifier: ParameterModifier
TypeParameter
id: Int
id: String
annotations: Annotation*
typeParameters : TypeParameter*
variance: Variance

View File

@ -5,7 +5,7 @@ Type
prefix : SimpleType
id: String
ParameterRef
id: Int
id: String
Singleton
path: Path
EmptyType