Give Scoped a default equals/hashCode implementation

This commit is contained in:
Dale Wijnand 2018-03-27 01:36:41 +01:00
parent 2e86eed151
commit 3692db9068
No known key found for this signature in database
GPG Key ID: 4F256E3D151DF5EF
5 changed files with 175 additions and 4 deletions

View File

@ -432,6 +432,10 @@ lazy val mainSettingsProj = (project in file("main-settings"))
mimaSettings,
mimaBinaryIssueFilters ++= Seq(
exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"),
// added a method to a sealed trait
exclude[InheritedNewAbstractMethodProblem]("sbt.Scoped.canEqual"),
exclude[InheritedNewAbstractMethodProblem]("sbt.ScopedTaskable.canEqual"),
),
)
.configure(

View File

@ -17,7 +17,18 @@ import sbt.Def.{ Initialize, KeyedInitialize, ScopedKey, Setting, setting }
import std.TaskExtra.{ task => mktask, _ }
/** An abstraction on top of Settings for build configuration and task definition. */
sealed trait Scoped { def scope: Scope; val key: AttributeKey[_] }
sealed trait Scoped extends Equals {
def scope: Scope
val key: AttributeKey[_]
override def equals(that: Any) =
(this eq that.asInstanceOf[AnyRef]) || (that match {
case that: Scoped => scope == that.scope && key == that.key && canEqual(that)
case _ => false
})
override def hashCode() = (scope, key).##
}
/** A common type for SettingKey and TaskKey so that both can be used as inputs to tasks.*/
sealed trait ScopedTaskable[T] extends Scoped {
@ -95,6 +106,8 @@ sealed abstract class SettingKey[T]
final def withRank(rank: Int): SettingKey[T] =
SettingKey(AttributeKey.copyWithRank(key, rank))
def canEqual(that: Any): Boolean = that.isInstanceOf[SettingKey[_]]
}
/**
@ -163,6 +176,8 @@ sealed abstract class TaskKey[T]
final def withRank(rank: Int): TaskKey[T] =
TaskKey(AttributeKey.copyWithRank(key, rank))
def canEqual(that: Any): Boolean = that.isInstanceOf[TaskKey[_]]
}
/**
@ -195,6 +210,8 @@ sealed trait InputKey[T]
final def withRank(rank: Int): InputKey[T] =
InputKey(AttributeKey.copyWithRank(key, rank))
def canEqual(that: Any): Boolean = that.isInstanceOf[InputKey[_]]
}
/** Methods and types related to constructing settings, including keys, scopes, and initializations. */

View File

@ -92,9 +92,13 @@ object BuildSettingsInstances {
type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped }
def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_))
def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_))
def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_))
final case class Label(value: String)
val genLabel: Gen[Label] = Gen.identifier map Label
implicit def arbLabel: Arbitrary[Label] = Arbitrary(genLabel)
def genInputKey[A: Manifest]: Gen[InputKey[A]] = genLabel map (x => InputKey[A](x.value))
def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = genLabel map (x => SettingKey[A](x.value))
def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = genLabel map (x => TaskKey[A](x.value))
def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary {
Gen.frequency(

View File

@ -0,0 +1,145 @@
/*
* sbt
* Copyright 2011 - 2017, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under BSD-3-Clause license (see LICENSE)
*/
package sbt.test
import org.scalacheck._, Prop._, util.Pretty
import sbt.internal.util.AttributeKey
import sbt.util.NoJsonWriter
import sbt.{ InputTask, Scope, Task }
import sbt.{ InputKey, Scoped, SettingKey, TaskKey }
import BuildSettingsInstances._
object ScopedSpec extends Properties("Scoped") {
val intManifest = manifest[Int]
val stringManifest = manifest[String]
implicit val arbManifest: Arbitrary[Manifest[_]] =
Arbitrary(Gen.oneOf(intManifest, stringManifest))
property("setting keys are structurally equal") = {
forAll { (label: Label, manifest: Manifest[_], scope: Scope) =>
val k1 = settingKey(label, manifest, scope)
val k2 = settingKey(label, manifest, scope)
expectEq(k1, k2)
}
}
property("task keys are structurally equal") = {
forAll { (label: Label, manifest: Manifest[_], scope: Scope) =>
val k1 = taskKey(label, manifest, scope)
val k2 = taskKey(label, manifest, scope)
expectEq(k1, k2)
}
}
property("input keys are structurally equal") = {
forAll { (label: Label, manifest: Manifest[_], scope: Scope) =>
val k1 = inputKey(label, manifest, scope)
val k2 = inputKey(label, manifest, scope)
expectEq(k1, k2)
}
}
property("different key types are not equal") = {
forAll { (label: Label, manifest: Manifest[_], scope: Scope) =>
val settingKey1 = settingKey(label, manifest, scope)
val taskKey1 = taskKey(label, manifest, scope)
val inputKey1 = inputKey(label, manifest, scope)
all(
expectNe(settingKey1, taskKey1),
expectNe(settingKey1, inputKey1),
expectNe(taskKey1, inputKey1),
)
}
}
property("different key types, with the same manifest, are not equal") = {
forAll { (label: Label, scope: Scope) =>
val prop1 = {
val manifest1 = manifest[Task[String]]
val attrKey = attributeKey(label, manifest1)
val k1 = SettingKey(attrKey) in scope
val k2 = TaskKey(attrKey) in scope
expectNeSameManifest(k1, k2)
}
val prop2 = {
val manifest1 = manifest[InputTask[String]]
val attrKey = attributeKey(label, manifest1)
val k1 = SettingKey(attrKey) in scope
val k2 = InputKey(attrKey) in scope
expectNeSameManifest(k1, k2)
}
all(prop1, prop2)
}
}
///
def settingKey[A](label: Label, manifest: Manifest[A], scope: Scope): SettingKey[A] = {
val noJsonWriter = NoJsonWriter[A]()
SettingKey[A](label.value)(manifest, noJsonWriter) in scope
}
def taskKey[A](label: Label, manifest: Manifest[A], s: Scope): TaskKey[A] =
TaskKey[A](label.value)(manifest) in s
def inputKey[A](label: Label, manifest: Manifest[A], scope: Scope): InputKey[A] =
InputKey[A](label.value)(manifest) in scope
def attributeKey[A](label: Label, manifest: Manifest[A]): AttributeKey[A] = {
val jsonWriter = NoJsonWriter[A]()
AttributeKey[A](label.value)(manifest, jsonWriter)
}
///
def expectEq(k1: Scoped, k2: Scoped): Prop =
?=(k1, k2) && ?=(k2, k1) map eqLabels(k1, k2)
def expectNe(k1: Scoped, k2: Scoped): Prop =
!=(k1, k2) && !=(k2, k1) map eqLabels(k1, k2)
def expectNeSameManifest(k1: Scoped, k2: Scoped) = {
all(
?=(k1.key.manifest, k2.key.manifest), // sanity check the manifests are the same
expectNe(k1, k2),
)
}
def eqLabels(k1: Scoped, k2: Scoped): Prop.Result => Prop.Result = r => {
val eqLabel = k1.key.label == k2.key.label
val eqManifest = k1.key.manifest == k2.key.manifest
val eqScope = k1.scope == k2.scope
r.label(s"label equality: ${k1.key.label} == ${k2.key.label} : $eqLabel")
.label(s"manifest equality: ${k1.key.manifest} == ${k2.key.manifest} : $eqManifest")
.label(s"scope equality: ${k1.scope} == ${k2.scope} : $eqScope")
}
def ?=[T](x: T, y: T)(implicit pp: T => Pretty): Prop =
if (x == y) proved
else
falsified :| {
val act = Pretty.pretty[T](x, Pretty.Params(0))
val exp = Pretty.pretty[T](y, Pretty.Params(0))
s"Expected $act to be equal to $exp"
}
def !=[T](x: T, y: T)(implicit pp: T => Pretty): Prop =
if (x == y) falsified
else
proved :| {
val act = Pretty.pretty[T](x, Pretty.Params(0))
val exp = Pretty.pretty[T](y, Pretty.Params(0))
s"Expected $act to NOT be equal to $exp"
}
}

View File

@ -1,6 +1,7 @@
scalaVersion := "2.12.4"
scalacOptions ++= Seq("-feature", "-language:postfixOps")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.2.0")
addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5")
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2")