diff --git a/compile/api/src/main/scala/xsbt/api/HashAPI.scala b/compile/api/src/main/scala/xsbt/api/HashAPI.scala index 59b59797c..02cc6aded 100644 --- a/compile/api/src/main/scala/xsbt/api/HashAPI.scala +++ b/compile/api/src/main/scala/xsbt/api/HashAPI.scala @@ -139,16 +139,29 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean, include { hash = startHash(0) hashSymmetric(s.packages, hashPackage) - hashDefinitions(s.definitions, true) + hashDefinitions(s.definitions, topLevel = true, isTrait = false) finalizeHash } def hashPackage(p: Package) = hashString(p.name) + @deprecated("Use the overload that indicates if the enclosing definition is a trait.", "0.14") def hashDefinitions(ds: Seq[Definition], topLevel: Boolean): Unit = + hashDefinitions(ds, topLevel, isTrait = false) + + def hashDefinitions(ds: Seq[Definition], topLevel: Boolean, isTrait: Boolean): Unit = { + // If the enclosing definition is a trait, then we must include private vars in the API hash + // of the trait, because scalac will generate setters and getters for these vars in the traits + // implementors. + val traitPrivateVars = + if (!includePrivate && !topLevel && isTrait) { + def isPublic(d: Definition): Boolean = d.access match { case _: xsbti.api.Public => true; case _ => false } + ds.collect { case v: Var if !isPublic(v) => v } + } else Seq.empty + val defs = SameAPI.filterDefinitions(ds, topLevel, includePrivate) - hashSymmetric(defs, hashDefinition) + hashSymmetric(traitPrivateVars ++ defs, hashDefinition) } /** @@ -356,14 +369,9 @@ final class HashAPI(includePrivate: Boolean, includeParamNames: Boolean, include def hashStructure0(structure: Structure, includeDefinitions: Boolean, isTrait: Boolean = false): Unit = { extend(StructureHash) hashTypes(structure.parents, includeDefinitions) - if (isTrait && !includeDefinitions) { - def public(d: Definition): Boolean = d.access match { case _: xsbti.api.Public => true; case _ => false } - hashDefinitions(structure.declared.filterNot(public), isTrait) - hashDefinitions(structure.inherited.filterNot(public), isTrait) - } if (includeDefinitions) { - hashDefinitions(structure.declared, isTrait) - hashDefinitions(structure.inherited, isTrait) + hashDefinitions(structure.declared, topLevel = false, isTrait) + hashDefinitions(structure.inherited, topLevel = false, isTrait) } } def hashParameters(parameters: Seq[TypeParameter], base: Type): Unit = diff --git a/compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala b/compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala index 4849838b2..470254dda 100644 --- a/compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala +++ b/compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala @@ -165,24 +165,25 @@ class NameHashingSpecification extends Specification { } /** - * Checks that private members are included in the hash of the public API of traits. - * Including the private members of traits is required because classes that implement a trait - * have to define the private members of the trait. Therefore, if a private member of a trait is added, - * modified or removed we need to recompile the classes that implement this trait. + * Checks that private vars are included in the hash of the public API of traits. + * Including the private vars of traits is required because classes that implement a trait + * have to define getters and setters for these vars. * For instance, if trait Foo is initially defined as: - * trait Foo { private val x = new A } + * trait Foo { private var x = new A } * changing it to - * trait Foo { private val x = new B } + * trait Foo { private var x = new B } * requires us to recompile all implementors of trait Foo, because scalac generates setters and getters - * for the private fields of trait Foo in its implementor. If the clients of trait Foo are not recompiled, + * 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 only. No other private member needs to be included. */ - "private members in traits" in { - /* trait Foo { private val x } */ + "private var in traits are included in API hash" in { + /* trait Foo { private var x } */ val fooTrait1 = simpleTrait("Foo", - simpleStructure(new Val(emptyType, "x", privateAccess, defaultModifiers, Array.empty)), + simpleStructure(new Var(emptyType, "x", privateAccess, defaultModifiers, Array.empty)), publicAccess) /* trait Foo */ @@ -198,15 +199,75 @@ class NameHashingSpecification extends Specification { } + "private vals in traits are NOT included in API hash" in { + /* trait Foo { private val x } */ + val fooTrait1 = + simpleTrait("Foo", + simpleStructure(new Val(emptyType, "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 def in traits are not included in API hash" in { + /* trait Foo { private def x } */ + val fooTrait1 = + simpleTrait("Foo", + simpleStructure(new Def(Array.empty, emptyType, 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 types in traits are included not in API hash" in { + /* trait Foo { private type x } */ + val fooTrait1 = + simpleTrait("Foo", + simpleStructure(new TypeAlias(emptyType, 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) + + } + /** - * Checks that private members in non-top-level traits are included as well. + * Checks that private vars in non-top-level traits are included as well. */ "private members in nested traits" in { - /* class A { trait Foo { private val x } } */ + /* class A { trait Foo { private var x } } */ val classA1 = simpleClass("A", simpleTrait("Foo", - simpleStructure(new Val(emptyType, "x", privateAccess, defaultModifiers, Array.empty)), + simpleStructure(new Var(emptyType, "x", privateAccess, defaultModifiers, Array.empty)), publicAccess)) /* class A { trait Foo } */ @@ -227,11 +288,11 @@ class NameHashingSpecification extends Specification { * Checks that private traits are NOT included in the hash. */ "private traits" in { - /* class Foo { private trait T { private val x } } */ + /* class Foo { private trait T { private var x } } */ val classFoo1 = simpleClass("Foo", simpleTrait("T", - simpleStructure(new Val(emptyType, "x", privateAccess, defaultModifiers, Array.empty)), + simpleStructure(new Var(emptyType, "x", privateAccess, defaultModifiers, Array.empty)), privateAccess)) /** class Foo { private trait T } */ @@ -256,10 +317,10 @@ class NameHashingSpecification extends Specification { * Checks that private members are NOT included in the hash of the public API of classes. */ "private members in classes are not included in the api hash" in { - /* class Foo { private val x } */ + /* class Foo { private var x } */ val classFoo1 = simpleClass("Foo", - simpleStructure(new Val(emptyType, "x", privateAccess, defaultModifiers, Array.empty))) + simpleStructure(new Var(emptyType, "x", privateAccess, defaultModifiers, Array.empty))) /* class Foo */ val classFoo2 = @@ -274,9 +335,9 @@ class NameHashingSpecification extends Specification { } /** - * Checks that private members do NOT contribute to name hashes. - * Test for https://github.com/sbt/sbt/issues/2324 - */ + * Checks that private members do NOT contribute to name hashes. + * Test for https://github.com/sbt/sbt/issues/2324 + */ "private members in classes do not contribute to name hashes" in { /* class Foo { private val x } */ val classFoo =