Hash of traits: include private fields, objects and super accessors

This commit is contained in:
Martin Duhem 2016-02-10 18:15:44 +01:00
parent edc1174468
commit 6576844698
11 changed files with 91 additions and 20 deletions

View File

@ -261,7 +261,7 @@ object ClassToAPI {
def modifiers(i: Int): api.Modifiers =
{
import Modifier.{ isAbstract, isFinal }
new api.Modifiers(isAbstract(i), false, isFinal(i), false, false, false, false)
new api.Modifiers(isAbstract(i), false, isFinal(i), false, false, false, false, false)
}
def access(i: Int, pkg: Option[String]): api.Access =
{

View File

@ -12,7 +12,7 @@ object APIUtil {
}
val byteToModifiers = (b: Byte) => {
def x(bit: Int) = (b & (1 << bit)) != 0
new Modifiers(x(0), x(1), x(2), x(3), x(4), x(5), x(6))
new Modifiers(x(0), x(1), x(2), x(3), x(4), x(5), x(6), x(7))
}
def isScalaSourceName(name: String): Boolean = name.endsWith(".scala")

View File

@ -151,17 +151,25 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean, include
def hashDefinitions(ds: Seq[Definition], topLevel: Boolean, isTrait: Boolean): Unit =
{
// If the enclosing definition is a trait, then we must include private vars and vals in the API hash
// of the trait, because scalac will generate setters, getters and fields for them in the traits
// implementors.
val traitPrivateFields =
def isPublic(d: Definition): Boolean = d.access match { case _: xsbti.api.Public => true; case _ => false }
def isTraitBreaker(d: Definition): Boolean = d match {
// Vars and vals in traits introduce getters, setters and fields in the implementing classes.
// See test `source-dependencies/trait-private-var
case _: FieldLike => true
// Objects in traits introduce fields in the implementing classes.
// See test `source-dependencies/trait-private-object`
case cl: ClassLike => cl.definitionType == DefinitionType.Module
// super calls introduce accessors that are not part of the public API
case d: Def => d.modifiers.isSuperAccessor
case _ => false
}
val includedPrivateDefinitions =
if (!includePrivate && !topLevel && isTrait) {
def isPublic(d: Definition): Boolean = d.access match { case _: xsbti.api.Public => true; case _ => false }
ds.collect { case fl: FieldLike if !isPublic(fl) => fl }
ds filter (x => isTraitBreaker(x) && !isPublic(x))
} else Seq.empty
val defs = SameAPI.filterDefinitions(ds, topLevel, includePrivate)
hashSymmetric(traitPrivateFields ++ defs, hashDefinition)
hashSymmetric(includedPrivateDefinitions ++ defs, hashDefinition)
}
/**

View File

@ -176,8 +176,6 @@ class NameHashingSpecification extends Specification {
* for the private vars of trait Foo in its implementor. If the clients of trait Foo are not recompiled,
* we get abstract method errors at runtime, because the types expected by the setter (for instance) does not
* match.
*
* NOTE: This logic is important for vars and vals only. No other private member needs to be included.
*/
"private var in traits are included in API hash" in {
/* trait Foo { private var x } */
@ -219,7 +217,28 @@ class NameHashingSpecification extends Specification {
}
"private def in traits are not included in API hash" in {
"private objects in traits are included in API hash" in {
/* trait Foo { private object x } */
val fooTrait1 =
simpleTrait("Foo",
simpleStructure(
new ClassLike(DefinitionType.Module, lzy(emptyType), lzy(simpleStructure()), Array.empty, Array.empty, "x", privateAccess, defaultModifiers, Array.empty)),
publicAccess)
/* trait Foo */
val fooTrait2 =
simpleTrait("Foo",
simpleStructure(),
publicAccess)
val api1 = new SourceAPI(Array.empty, Array(fooTrait1))
val api2 = new SourceAPI(Array.empty, Array(fooTrait2))
HashAPI(api1) !== HashAPI(api2)
}
"private non-synthetic def in traits are not included in API hash" in {
/* trait Foo { private def x } */
val fooTrait1 =
simpleTrait("Foo",
@ -239,6 +258,27 @@ class NameHashingSpecification extends Specification {
}
"private synthetic def in traits are included in API hash" in {
/* trait Foo { private <superaccessor> def x } */
val modifiers = new xsbti.api.Modifiers(false, false, false, false, false, false, false, true)
val fooTrait1 =
simpleTrait("Foo",
simpleStructure(new Def(Array.empty, emptyType, Array.empty, "x", privateAccess, modifiers, Array.empty)),
publicAccess)
/* trait Foo */
val fooTrait2 =
simpleTrait("Foo",
simpleStructure(),
publicAccess)
val api1 = new SourceAPI(Array.empty, Array(fooTrait1))
val api2 = new SourceAPI(Array.empty, Array(fooTrait2))
HashAPI(api1) !== HashAPI(api2)
}
"private types in traits are included not in API hash" in {
/* trait Foo { private type x } */
val fooTrait1 =
@ -262,7 +302,7 @@ class NameHashingSpecification extends Specification {
/**
* Checks that private vars in non-top-level traits are included as well.
*/
"private members in nested traits" in {
"private variables in nested traits are include in the API hash" in {
/* class A { trait Foo { private var x } } */
val classA1 =
simpleClass("A",
@ -287,7 +327,7 @@ class NameHashingSpecification extends Specification {
/**
* Checks that private traits are NOT included in the hash.
*/
"private traits" in {
"private inner traits are not included in the API hash" in {
/* class Foo { private trait T { private var x } } */
val classFoo1 =
simpleClass("Foo",
@ -397,6 +437,6 @@ class NameHashingSpecification extends Specification {
private val strTpe = new Projection(emptyType, "String")
private val publicAccess = new Public
private val privateAccess = new Private(new Unqualified)
private val defaultModifiers = new Modifiers(false, false, false, false, false, false, false)
private val defaultModifiers = new Modifiers(false, false, false, false, false, false, false, false)
}

View File

@ -76,7 +76,7 @@ object TestCaseGenerators {
private[this] def makeDefinition(name: String): Definition =
new ClassLike(DefinitionType.ClassDef, lzy(new EmptyType()),
lzy(new Structure(lzy(Array()), lzy(Array()), lzy(Array()))), Array(), Array(),
name, new Public(), new Modifiers(false, false, false, false, false, false, false), Array())
name, new Public(), new Modifiers(false, false, false, false, false, false, false, false), Array())
private[this] def lzy[T <: AnyRef](x: T) = SafeLazy.strict(x)

View File

@ -438,7 +438,7 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType,
val absOver = s.hasFlag(ABSOVERRIDE)
val abs = s.hasFlag(ABSTRACT) || s.hasFlag(DEFERRED) || absOver
val over = s.hasFlag(OVERRIDE) || absOver
new xsbti.api.Modifiers(abs, over, s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY), hasMacro(s))
new xsbti.api.Modifiers(abs, over, s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY), hasMacro(s), s.hasFlag(SUPERACCESSOR))
}
private def isImplicit(s: Symbol) = s.hasFlag(Flags.IMPLICIT)

View File

@ -9,13 +9,14 @@ public final class Modifiers implements java.io.Serializable
private static final int ImplicitBit = 4;
private static final int LazyBit = 5;
private static final int MacroBit = 6;
private static final int SuperAccessorBit = 7;
private static int flag(boolean set, int bit)
{
return set ? (1 << bit) : 0;
}
public Modifiers(boolean isAbstract, boolean isOverride, boolean isFinal, boolean isSealed, boolean isImplicit, boolean isLazy, boolean isMacro)
public Modifiers(boolean isAbstract, boolean isOverride, boolean isFinal, boolean isSealed, boolean isImplicit, boolean isLazy, boolean isMacro, boolean isSuperAccessor)
{
this.flags = (byte)(
flag(isAbstract, AbstractBit) |
@ -24,7 +25,8 @@ public final class Modifiers implements java.io.Serializable
flag(isSealed, SealedBit) |
flag(isImplicit, ImplicitBit) |
flag(isLazy, LazyBit) |
flag(isMacro, MacroBit)
flag(isMacro, MacroBit) |
flag(isSuperAccessor, SuperAccessorBit)
);
}
@ -68,6 +70,10 @@ public final class Modifiers implements java.io.Serializable
{
return flag(MacroBit);
}
public final boolean isSuperAccessor()
{
return flag(SuperAccessorBit);
}
public boolean equals(Object o)
{
return (o instanceof Modifiers) && flags == ((Modifiers)o).flags;
@ -78,6 +84,6 @@ public final class Modifiers implements java.io.Serializable
}
public String toString()
{
return "Modifiers(" + "isAbstract: " + isAbstract() + ", " + "isOverride: " + isOverride() + ", " + "isFinal: " + isFinal() + ", " + "isSealed: " + isSealed() + ", " + "isImplicit: " + isImplicit() + ", " + "isLazy: " + isLazy() + ", " + "isMacro: " + isMacro()+ ")";
return "Modifiers(" + "isAbstract: " + isAbstract() + ", " + "isOverride: " + isOverride() + ", " + "isFinal: " + isFinal() + ", " + "isSealed: " + isSealed() + ", " + "isImplicit: " + isImplicit() + ", " + "isLazy: " + isLazy() + ", " + "isMacro: " + isMacro()+ ", isSuperAccessor:" + isSuperAccessor() + ")";
}
}

View File

@ -0,0 +1,3 @@
trait A {
val foo = 0
}

View File

@ -0,0 +1,5 @@
object B extends A {
def main(args: Array[String]): Unit = {
println(foo)
}
}

View File

@ -0,0 +1,4 @@
trait A {
val foo = 0 + X.a
private object X { val a = 1 }
}

View File

@ -0,0 +1,5 @@
> run
$ copy-file changes/A.scala A.scala
> run