diff --git a/README.md b/README.md index 6629066d..3b7bbf89 100644 --- a/README.md +++ b/README.md @@ -2,59 +2,56 @@ This library aims to prevent compilation of code comparing arbitrary values unless equality is specifically supported for their respective types. -* Any operations adhering to the principle above are considered to be **equality-safe**. +* Any operations adhering to the principle above are considered to be *equality-safe*. * A `Product` type (case class, enum, tuple) can support equality only if the types of all its fields support equality. -This makes equality behave like using [Data.Eq](https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Eq.html) in Haskell or [std::cmp::Eq](https://doc.rust-lang.org/beta/std/cmp/trait.Eq.html) in Rust. +This makes equality behave similarly to [Data.Eq](https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Eq.html) in Haskell or [std::cmp::Eq](https://doc.rust-lang.org/beta/std/cmp/trait.Eq.html) in Rust. -The following features are provided based on the compiler [strict equality](https://docs.scala-lang.org/scala3/book/ca-multiversal-equality.html#allowing-the-comparison-of-class-instances) support: -* [Eq type class](#eq-type-class) - strict equality type class with automatic derivation for `Product` types -* [Standard Eq instances](#standard-eq-instances) - equality type class instances for relevant Java and Scala standard library types -* [Collection extensions](#collection-extensions) - equality-safe extension methods for standard Scala collections -* [Strict equality opt-out](#strict-equality-opt-out) - escape hatch to enable universal equality within a specific scope +The following features are provided leveraging the compiler [strict equality](https://docs.scala-lang.org/scala3/book/ca-multiversal-equality.html#allowing-the-comparison-of-class-instances) support: +* [Eq type class](#eq-type-class) - **value equality** type class with automatic **derivation** for `Product` types +* [EqRef type class](#eqref-type-class) - **reference equality** type class with automatic **derivation** for non-extensible types +* [Standard equality instances](#standard-instances) - equality **support** for relevant Java and Scala **standard library types** +* [Collection extensions](#collection-extensions) - **equality-safe** extension methods for standard Scala **collections** +* [Hybrid equality](#hybrid-equality) - **combined** use of equality constructs for both **value and reference equality** +* [Universal equality](#universal-equality) - **escape hatch** to enable **default Scala behavior** concerning equality
-![](https://github.com/antognini/type-safe-equality/blob/main/site/example-ide-1h.png) +![](https://github.com/antognini/type-safe-equality/blob/main/doc/example-ide-1k.png)
-Please see the [FAQ](#faq) for additional information. +Please see [feature selection](#feature-selection) and [FAQ](#faq) for additional information. # Quickstart ## New Project +Execute the following commands: ```scala sbt new antognini/type-safe-equality.g8 cd type-safe-equality-example -sbt run +sbt console ``` -## REPL +Run any of the examples shown below by copy & pasting them into REPL. -Execute any of the examples by copy & paste them in REPL: -```scala -sbt new antognini/type-safe-equality.g8 -cd type-safe-equality-example -sbt console -``` ## Embed in your project -This library requires **[Scala 3.3](https://scala-lang.org/blog/2023/05/30/scala-3.3.0-released.html)+** on **[Java 11](https://en.wikipedia.org/wiki/Java_version_history)+**. +This library requires **[Scala 3.3](https://scala-lang.org/blog/2023/05/30/scala-3.3.0-released.html)+** on +**[Java 11](https://en.wikipedia.org/wiki/Java_version_history)+**. Include the library dependency in your `build.sbt` and enable strict equality: ```scala -libraryDependencies += "ch.produs" %% "type-safe-equality" % "0.5.0" +libraryDependencies += "ch.produs" %% "type-safe-equality" % "0.6.0" scalacOptions += "-language:strictEquality" -scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality.all" +scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality" ``` -## Try it out! - +Try it out: ```scala import java.time.LocalDateTime import java.util.jar.Attributes @@ -68,11 +65,11 @@ given Eq[Attributes] = Eq.assumed Attributes() == Attributes() // Derive equality for a product type -case class Box[A: Eq]( +case class Box[T: Eq]( name: String, - item: Either[String, A], + item: Either[String, T], ) derives Eq -val box = Box("my box", Right(Attributes())) +val box = Box("", Right(Attributes())) box == box // Use an equality-safe alternative to .contains() @@ -80,111 +77,138 @@ val names = List(now, now) names.contains_eq(now) ``` + # Features -The examples shown below assume strict equality enabled (unless otherwise stated). +All features described below assume that strict equality compiler flag is enabled. + ## Eq type class -[Eq](https://github.com/antognini/type-safe-equality/blob/main/main/src/main/scala/equality/Eq.scala) -is a type class providing type safe use of the `==` and `!=` operators and verification of equality safety for composed data types. +[Eq](https://github.com/antognini/type-safe-equality/blob/main/equality/src/main/scala/equality/core/Eq.scala) +is a type class providing type safe use of `==` and `!=` as *value equality* operators and +automatic verification of *value equality* support for product types. -Eq instances can be obtained in the following ways: +Eq instances for an arbitrary type `T` can be obtained in one of the following ways: -* By asking the compiler to **verify** (derive) equality safety - * `derives Eq` - * `given Eq[X] = Eq.derived` +* By asking the compiler to **verify** (derive) *value equality* support + * ` derives Eq` + * `given Eq[T] = Eq.derived` -* By telling the compiler to **assume** equality safety (fallback with no checking) - * `derives Eq.assumed` - * `given Eq[X] = Eq.assumed` +* By telling the compiler to **assume** *value equality* support (fallback with no checking) + * ` derives Eq.assumed` + * `given Eq[T] = Eq.assumed` + +**Note**: It is recommended to assume *value equality* support only if it is not possible to verify it. + +Eq limits the `==` and `!=` operators to compare values only and +only if given Eq instance is in scope for the compared type. -**Note**: It is recommended to assume equality only if it is not possible to verify it. +This causes the following differences from the default Scala behavior: +- It completely disallows comparison of values for unrelated or unsupported types. +- It practically disables the use `==` and `!=` operators for comparing references. ### Verifying equality -Verified equality for composed case classes via type class derivation: +Verify equality for composed case classes via type class derivation: ```scala case class Email(address: String) derives Eq -// Only compiles because class Email derives Eq +// Compiles because Email derives Eq case class Person( name: String, contact: Email, ) derives Eq -val person = Person("Alice", Email("alice@maluma.osw")) +val person = Person("Alice", Email("alice@example.net")) -// Only compiles because class Person derives Eq +// Compiles because Person derives Eq person == person ``` -Verified equality for composed case classes with type parameters via type class derivation: +Verify equality for composed case classes with type parameters via type class derivation: ```scala case class Email(address: String) derives Eq -// Only compiles because the type parameter A is declared with a context bound [A: Eq] -case class Person[A: Eq]( +// Compiles because the type parameter T is declared with a context bound [T: Eq] +case class Person[T: Eq]( name: String, - contact: A, + contact: T, ) derives Eq -// Only compiles because class Email derives Eq -val person = Person("Alice", Email("alice@maluma.osw")) +// Compiles because Email derives Eq +val person = Person("Alice", Email("alice@example.net")) -// Only compiles because class Person derives Eq +// Compiles because Person derives Eq person == person ``` -Verified equality for an existing arbitrary class with a given using the same derivation mechanism: +Verify equality for an enum with a given using the same derivation mechanism: ```scala enum Weekday: case Monday, Tuesday, Wednesday // ... +// Compiles because Weekday is a product type with all options supporting equality given Eq[Weekday] = Eq.derived -// Only compiles because given Eq type class instance for Weekday is in scope +// Compiles because given Eq type class instance for Weekday is in scope Weekday.Monday == Weekday.Monday ``` ### Assuming equality -Assumed equality for the bottom classes of a class hierarchy via type class derivation: +Assume equality for the bottom classes of a class hierarchy via type class derivation: ```scala class Animal case class Cat() extends Animal derives Eq.assumed -case class Dog() extends Animal derives Eq.assumed +case class Dog() extends Animal + +val cat = Cat() +val dog = Dog() + +// Compiles because the selected bottom class of this hierarchy derives Eq +cat == cat -// Values of type Cat can compare each other with == or != -// Values of type Dog can compare each other with == or != -// Within this hierarchy, any other comparison with == or != fails +dog == dog +// ERROR: Values of types Dog and Dog cannot be compared with == or != + +cat != dog +// ERROR: Values of types Cat and Dog cannot be compared with == or != ``` -Assumed equality for the base class of a class hierarchy via type class derivation: +Assume equality for the base class of a class hierarchy via type class derivation: ```scala class Animal derives Eq.assumed case class Cat() extends Animal case class Dog() extends Animal -// Within this hierarchy, pairwise comparison with == or != between values of any type is allowed +val cat = Cat() +val dog = Dog() + +// Compiles because the base class of this hierarchy derives Eq +cat == cat +dog == dog +cat != dog ``` -Assumed equality for an existing arbitrary class with a given: +Assume equality for an existing arbitrary type with a given: ```scala import java.util.jar.Attributes -// Always compiles given Eq[Attributes] = Eq.assumed -// Only compiles with the given instance above -Attributes() == Attributes() +val attributes = Attributes() + +// Compiles because given Eq type class instance for Attributes is in scope +attributes == attributes ``` + ### Eq verification rules -In order to successfully verify (derive) equality for a type `T` all of following conditions must be satisfied: +In order to successfully verify (derive) equality for a type `T` all following conditions must be satisfied: 1. Type `T` is a `Product` 2. For each field of type `F`: @@ -208,46 +232,125 @@ case class MyClass[A: Eq, B: Eq, C: Eq, D: Eq]( // Rule 2.ii.a B is declared with a context bound [B: Eq] box: Box[B], - // Rule 2.i given instance of Eq[Couple[?, Seq[?]]] is available in the current scope + // Rule 2.i given instance of Eq[Pair[?, Seq[?]]] is available in the current scope // Rule 2.ii.a C is declared with a context bound [C: Eq] // Rule 2.ii.a D is declared with a context bound [D: Eq] - couple: Couple[C, Seq[D]] + pair: Pair[C, Seq[D]] ) derives Eq ``` -## Standard Eq instances +## EqRef type class + +[EqRef](https://github.com/antognini/type-safe-equality/blob/main/equality/src/main/scala/equality/core/EqRef.scala) +is a type class providing type safe use of `eqRef` and `neRef` as *reference equality* operators and +automatic verification of *reference equality* support for non-extensible types. +These provide substitutes for the built-in `eq` and `ne` operators which are not type safe. + +EqRef instances for an arbitrary type `T` can be obtained in one of the following ways: + +* By asking the compiler to **verify** (derive) *reference equality* support **[ currently implemented but not verifying ]** + * ` derives EqRef` + * `given EqRef[T] = EqRef.derived` + +* By telling the compiler to **assume** *reference equality* support (fallback with no checking) + * ` derives EqRef.assumed` + * `given EqRef[T] = EqRef.assumed` + +**Note**: It is recommended to assume *reference equality* support only if it is not possible to verify it. + +EqRef introduces `eqRef` and `neRef` methods which compare references only and +only if given EqRef instance is in scope for the compared type. + +This causes the following differences from the default Scala behavior: +- It completely disallows comparison of references for unrelated or unsupported types. + - It requires the use of `eqRef` and `neRef` operators in order to compare references. + + +### Verifying reference equality + +Verify equality for a non-extensible class via type class derivation: +```scala +// Compiles because Item cannot be extended and does not override the equals() method +final class Item(val id: String) derives EqRef + +val item = Item("") + +// Compiles because given EqRef type class instance for Item is in scope +item eqRef item +``` + +Verify equality for a non-extensible class with a given using the same derivation mechanism: +```scala +final class Item(id: String) + +// Compiles because Item is cannot be extended and overrides the equals() method +given EqRef[Item] = EqRef.derived + +val item = Item("") + +// Compiles because given EqRef type class instance for Item is in scope +item neRef item +``` + + +### Assuming reference equality + +Assume equality for an arbitrary class via type class derivation: +```scala +final class Item(val id: String) derives EqRef.assumed + +val item = Item("") + +// Compiles because Item derives EqRef +item eqRef item +``` + +Assume equality for an arbitrary class with a given using the same derivation mechanism: +```scala +final class Item(val id: String) + +given EqRef[Item] = EqRef.assumed + +val item = Item("") -This library provides **[Eq type class instances](site/StandardEqInstances.md)** for selected Java and Scala standard library types. +// Compiles because given EqRef type class instance for Item is in scope +item neRef item +``` + + +## Standard instances -Standard type class `Eq` instances work out of the box and provide type safe equality at no cost +This library provides **[Eq and EqRef type class instances](doc/StandardInstances.md)** for various commonly used Scala and Java standard library types. -**Note**: `Eq` instances are provided only for those standard library types where equality operation is guaranteed to make sense. +These type class instances exists only for types where value or *reference equality* makes sense and provide type safe equality at no cost. +Use standard *value equality* type class instance to compare standard temporal values: ```scala import java.time.{LocalDate, LocalDateTime} val now = LocalDateTime.now val later = LocalDateTime.now -// Only compiles because given Eq type class instance for LocalDateTime is in scope +// Compiles because given Eq type class instance for LocalDateTime is in scope now == later val today = LocalDate.now -today == now +today != now // ERROR: Values of types LocalDate and LocalDateTime cannot be compared with == or != ``` + ## Collection extensions Certain methods of specific standard library collection types (and their subtypes) are not equality-safe. This library provides the following equality-safe alternatives for such methods: -| Collection types | Original method | Equality-safe method | -|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------|--------------------------| +| Collection types | Original method | Equality-safe method | +|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------|------------------------| | [Seq](https://scala-lang.org/api/3.x/scala/collection/Seq.html) , [Iterator](https://scala-lang.org/api/3.x/scala/collection/Iterator.html) | `.contains` | `.contains_eq` | | [Seq](https://scala-lang.org/api/3.x/scala/collection/Seq.html) | `.containsSlice` | `.containsSlice_eq` | | [Seq](https://scala-lang.org/api/3.x/scala/collection/Seq.html) | `.diff` | `.diff_eq` | @@ -262,125 +365,125 @@ This library provides the following equality-safe alternatives for such methods: **Note**: [Set](https://scala-lang.org/api/3.x/scala/collection/Set.html) and [Map](https://scala-lang.org/api/3.x/scala/collection/Map.html) are generally equality-safe because they use invariant type parameters. -:warning: This feature requires enabling strict equality compiler option - -An example of using equality-safe collection methods: +Using equality-safe collection methods: ```scala case class Apple(x: String) derives Eq val appleA = Apple("A") val appleB = Apple("B") -val appleC = Apple("C") -val apples = List(appleA, appleB, appleC) +val apples = List(appleA, appleB) case class Car(x: String) derives Eq val carY = Car("Y") val carX = Car("X") val cars = List(carX, carY) -// It's pointless to search for a car in a list of apples +// Compiles but it should not since it is meaningless and always returns false apples.contains(carX) -// Type checks but it shouldn't --> yields false apples.contains_eq(carX) // ERROR: Values of types A and A cannot be compared with == or != // where: A is a type variable with constraint >: Apple | Car -// It is pointless to remove a list of cars from a list of apples +// Compiles but it should not since it is meaningless and always returns the original list apples.diff(cars) -// Type checks but it shouldn't --> returns the original list apples.diff_eq(cars) // ERROR: Values of types Apple and Apple | Car cannot be compared with == or != ``` -## Strict equality opt-out -In order to facilitate the transition to strict equality on existing codebases this library provides an opt-out from strict equality checking. +## Hybrid equality -Strict equality opt-out for an entire compilation unit: -```scala -// Import to disable strict equality -import equality.universal.given +Hybrid equality allows the use of value equality constructs also for reference equality. + +Enabling the hybrid equality has the following effects: +* `==` and `!=` operators can also compare references if given EqRef instance is in scope for the compared type. +* Eq derivation mechanism for product types supports fields with EqRef instances. + +**Note:** Mixing value and reference equality is generally discouraged and +should be limited to special cases which would cause difficulties otherwise. -// Only compiles because strict equality is disabled -val _ = 1 == "abc" +Hybrid equality for a specific scope can be enabled as follows: +```scala +import equality.hybrid.given ``` -Strict equality opt-out for a local scope: +Using hybrid equality: ```scala -def areEqual(x1: Any, x2: Any): Boolean = - // Import to locally disable strict equality - import equality.universal.given +class Item(val id: String) derives EqRef.assumed + +val item = Item("") - // Only compiles because strict equality is disabled - x1 == x2 +// Compiles because hybrid equality is enabled +item == item ``` -# Library feature selection +## Universal equality -This library relies on compiler imports to facilitate working on equality without imports in your code. However, you can choose which features you want to make available in your code. +Universal equality allows comparison of unrelated types which is the default Scala behavior. -## All-in-one +Universal equality for a specific scope can be enabled as follows: +```scala +import equality.universal.given +``` +Using universal equality: ```scala -scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality.all" +// Compiles because universal equality is enabled +1 == true ``` -## Minimal -With this setup you will only see the two basic classes of this library in your code. Everything else needs to be explicitly imported: +## Feature selection + +Multiple ways how to gradually enable various features of this library are described below. + +Build: ```scala scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality" ``` -Import every feature you want to use: +Build with hybrid equality: ```scala -// All types and equality type class instances for package scala -import equality.scala_.{*, given} - -// All types and equality type class instances for package scala.util -import equality.scala_util.{*, given} - -// Equality-safe collection extension -import equality.scala_collection.CollectionExtension.* - -// All types and equality type class instances for package java.time -import equality.java_time.{*, given} +scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality,equality.hybrid" +``` -// Equality type class instance for java.time.LocalDate -import equality.java_time.java_time_LocalDate +Import: +```scala +import equality.{*, given} +``` +Import with hybrid equality: +```scala +import equality.{*, given} +import equality.hybrid.given ``` -## Customized +Import without standard Eq and EqRef instances: ```scala -scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality" +import equality.core.{*, given} ``` -Define an import object: + +Import with specific standard Eq and EqRef instances and collection extensions only: ```scala -object MyEq { - // all types and Eq instances for scala.* - export equality.scala_.EqInstances.{*, given} +import equality.core.{*, given} - // all types and Eq instances for java.util.* - export equality.java_util.EqInstances.{*, given} +// Eq type class instances for types in package scala +import equality.scala_.{*, given} - // Eq instance for java.io.File - export equality.java_io.java_io_File +// Eq type class instances for types in package java.time +import equality.java_time.{*, given} - // Eq instance for java.nio.file.Path - export equality.java_nio_file.java_nio_file_Path +// Eq type class instance for java.text.Format +import equality.java_text.java_text_Format - // additionally defined Eq instance - given Eq[java.util.jar.Attributes] = Eq.assumed -} -``` +// EqRef type class instance for java.net.Socket +import equality.java_net.java_net_Socket -subsequently, use `MyEq` anywhere with -```scala -import MyEq.{*, given} +// Equality-safe collection extension +import equality.scala_collection.CollectionExtension.* ``` @@ -388,14 +491,14 @@ import MyEq.{*, given} ## What is the relation between `Eq` and `CanEqual`? -Each instance of `Eq` type class produces an instance of `CanEqual` thus making it compatible with established strict equality support in the Scala compiler. +Each instance of `Eq` type class produces an instance of `CanEqual` thus making it compatible with the established strict equality support in the Scala compiler. ## Is `CanEqual` good enough to model equality? `CanEqual` is a fairly low-level marker mechanism with following limitations: -* `CanEqual` does not support compile-time verification of equality safety for product types composed from other equality-safe types. +* `CanEqual` does not support compile-time verification of equality safety for product types composed of other equality-safe types. * `CanEqual` given instances are automatically provided for a few basic standard library types but for nothing else. Composition example with `CanEqual` not failing as it should: @@ -408,6 +511,7 @@ case class B(a: A) derives CanEqual B(A()) == B(A()) ``` + ## Why does `CanEqual[-L, -R]` have two type parameters and `Eq[-T]` only one ? See the motivation for `CanEqual[-L, -R]` in the documentation about [Scala 3 equality](https://docs.scala-lang.org/scala3/reference/contextual/multiversal-equality.html). @@ -426,7 +530,7 @@ This library focuses on strict equality and therefore the `Eq` type class can be ## Can types parametrized with `Any` support equality ? -No, they don't, because they would require for the type `Any` to support equality, which would defeat the purpose. +No, they don't because they would require for the type `Any` to support equality, which would defeat the purpose. @@ -444,12 +548,13 @@ Contrary to this principle, the compiler makes values of numeric types universal If universal equality for case classes parameterized with different numeric types is required, the following method can be used: ```scala -case class Box[A: Eq](a: A) derives Eq, CanEqual +case class Box[T: Eq](value: T) derives Eq, CanEqual -// Compiles because class Box additionally derives CanEqual -Box(1) == Box(2L) +// Compiles because Box also derives CanEqual +Box(1) == Box(1L) ``` + ## How to verify that strict equality is enabled in build settings ? ```scala @@ -457,8 +562,8 @@ Box(1) == Box(2L) checkStrictEqualityBuild() ``` -## +## Special thanks to -* **Martin Ockajak** \ No newline at end of file +* **Martin Ockajak** diff --git a/build.sbt b/build.sbt index 91c4198b..4e44cb8e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,28 +1,33 @@ // Project +val projectName = "type-safe-equality" ThisBuild / organization := "ch.produs" ThisBuild / organizationName := "produs ag" ThisBuild / organizationHomepage := None ThisBuild / description := "Scala 3 type safe equality" -ThisBuild / homepage := Some(url("https://github.com/antognini/type-safe-equality")) +ThisBuild / homepage := Some(url(s"https://github.com/antognini/$projectName")) ThisBuild / licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")) -ThisBuild / version := "0.5.0" -ThisBuild / scalaVersion := "3.3.0" +ThisBuild / version := "0.6.0" ThisBuild / versionScheme := Some("semver-spec") -ThisBuild / publishTo := sonatypePublishToBundle.value -ThisBuild / scmInfo := Some( - ScmInfo( - url("https://github.com/antognini/type-safe-equality"), - "scm:git@github.com:antognini/type-safe-equality.git" - )) ThisBuild / developers := List( Developer( id = "LA", name = "Luigi Antognini", email = "", url = url("https://github.com/antognini") - )) + ) +) +ThisBuild / scmInfo := Some( + ScmInfo( + url(s"https://github.com/antognini/$projectName"), + s"scm:git@github.com:antognini/$projectName.git" + ) +) +Global / onChangedBuildSource := ReloadOnSourceChanges -lazy val compileOptions = Seq( + +// Compile +ThisBuild / scalaVersion := "3.3.0" +ThisBuild / Compile / scalacOptions := Seq( "-encoding", "utf8", "-deprecation", "-language:scala3", @@ -33,34 +38,26 @@ lazy val compileOptions = Seq( // "-Wunused:all" ) -lazy val root = project - .in(file(".")) - .dependsOn(eq) - .settings( - name := "type-safe-equality", - publish / skip := true, - Compile / scalacOptions ++= compileOptions - ) - .aggregate(eq, examples) -lazy val eq = project - .in(file("eq")) - .settings( - libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.2.16" % "test" - ), - name := "type-safe-equality", - Compile / scalacOptions ++= compileOptions - ) +lazy val root = project.in(file(".")).dependsOn(equality).settings( + name := projectName, + publish / skip := true +).aggregate(equality, examples) -lazy val examples = project - .in(file("examples")) - .dependsOn(eq) - .settings( - publish / skip := true, - Compile / scalacOptions ++= compileOptions, - Compile / scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality.all" +lazy val equality = project.in(file("equality")).settings( + publishLocal := publishLocal.dependsOn(clean, Test / test).value, + publish := publish.dependsOn(clean, Test / test).value, + name := projectName, + libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.2.16" % "test" ) +) + +lazy val examples = project.in(file("examples")).dependsOn(equality).settings( + publish / skip := true, + Compile / scalacOptions += "-Yimports:scala,scala.Predef,java.lang,equality" +) + // Publish sonatypeRepository := "https://s01.oss.sonatype.org/service/local" @@ -79,3 +76,4 @@ credentials ++= Seq( Option(System.getenv("SONATYPE_PASSWORD")).getOrElse("") ) ) +ThisBuild / publishTo := sonatypePublishToBundle.value diff --git a/site/Numbers.md b/doc/Numbers.md similarity index 100% rename from site/Numbers.md rename to doc/Numbers.md diff --git a/doc/StandardInstances.md b/doc/StandardInstances.md new file mode 100644 index 00000000..955f60f8 --- /dev/null +++ b/doc/StandardInstances.md @@ -0,0 +1,118 @@ +**[Type safe equality for Scala 3](https://github.com/antognini/type-safe-equality)** + +## List of defined equality type class instances + +Equality works out of the box for the types and listed in this table, including all their subtypes. +Equality rules are inferred correctly on those types when using `Unit`, `Null` and `Nothing` as type parameters. + +
+ + +| Package | Instance | Type class | Equality for type | +|--------------------------------------|------------------------------------------------------------------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `equality.scala_` | `scala_Array` | Eq | [scala.Array](https://scala-lang.org/api/3.x/scala/Array.html) | +| | `scala_Boolean` | Eq | [scala.Boolean](https://scala-lang.org/api/3.x/scala/Boolean.html) | +| | `scala_Byte` | Eq | [scala.Byte](https://scala-lang.org/api/3.x/scala/Byte.html) | +| | `scala_Char` | Eq | [scala.Char](https://scala-lang.org/api/3.x/scala/Char.html) | +| | `scala_Double` | Eq | [scala.Double](https://scala-lang.org/api/3.x/scala/Double.html) | +| | `scala_Float` | Eq | [scala.Float](https://scala-lang.org/api/3.x/scala/Float.html) | +| | `scala_Int` | Eq | [scala.Int](https://scala-lang.org/api/3.x/scala/Int.html) | +| | `scala_Long` | Eq | [scala.Long](https://scala-lang.org/api/3.x/scala/Long.html) | +| | `scala_Nothing` | Eq | [scala.Nothing](https://scala-lang.org/api/3.x/scala/Nothing.html) | +| | `scala_Null` | Eq | [scala.Null](https://scala-lang.org/api/3.x/scala/Null.html) | +| | `scala_Option` | Eq | [scala.Option](https://scala-lang.org/api/3.x/scala/Option.html) | +| | `scala_Short` | Eq | [scala.Short](https://scala-lang.org/api/3.x/scala/Short.html) | +| | `scala_String` | Eq | [scala.String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html) | +| | `scala_Tuple0`
`scala_Tuple1`
...
`scala_Tuple31` | Eq | [scala.EmptyTuple](https://scala-lang.org/api/3.x/scala/EmptyTuple$.html)
`T1 *: EmptyTuple`
...
`T1 *: T2 *: ... *: T31 *: EmptyTuple` | +| | `scala_Unit` | Eq | [scala.Unit](https://scala-lang.org/api/3.x/scala/Unit.html) | +| `equality.scala_collection` | `scala_collection_Map` | Eq | [scala.collection.Map](https://scala-lang.org/api/3.x/scala/collection/Map.html) | +| | `scala_collection_Set` | Eq | [scala.collection.Set](https://scala-lang.org/api/3.x/scala/collection/Set.html) | +| | `scala_collection_Seq` | Eq | [scala.collection.Seq](https://scala-lang.org/api/3.x/scala/collection/Seq.html) | +| `equality.scala_concurrent` | `scala_concurrent_Future` | EqRef | [scala.concurrent.Future](https://scala-lang.org/api/3.x/scala/concurrent/Future.html) | +| | `scala_concurrent_Promise` | EqRef | [scala.concurrent.Promise](https://scala-lang.org/api/3.x/scala/concurrent/Promise.html) | +| `equality.scala_concurrent_duration` | `scala_concurrent_duration_Deadline` | Eq | [scala.concurrent.duration.Deadline](https://scala-lang.org/api/3.x/scala/concurrent/duration/Deadline.html) | +| | `scala_concurrent_duration_Duration` | Eq | [scala.concurrent.duration.Duration](https://scala-lang.org/api/3.x/scala/concurrent/duration/Duration.html) | +| `equality.scala_io` | `scala_io_Codec` | Eq | [scala.io_Codec](https://scala-lang.org/api/3.x/scala/io/Codec.html) | +| `equality.scala_math` | `scala_math_BigDecimal` | Eq | [scala.math.BigDecimal](https://scala-lang.org/api/3.x/scala/math/BigDecimal.html) | +| | `scala_math_BigInt` | Eq | [scala.math.BigInt](https://scala-lang.org/api/3.x/scala/math/BigInt.html) | +| `equality.scala_util` | `scala_util_Either` | Eq | [scala.util.Either](https://scala-lang.org/api/3.x/scala/util/Either.html) | +| | `scala_util_Try` | Eq | [scala.util.Try](https://scala-lang.org/api/3.x/scala/util/Try.html) | +| `equality.java_io` | `java_io_FileDescriptor` | Eq | [java.io.FileDescriptor](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/FileDescriptor.html) | +| | `java_io_File` | Eq | [java.io.File](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/File.html) | +| | `java_io_InputStream` | EqRef | [java.io.InputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html) | +| | `java_io_OutputStream` | EqRef | [java.io.OutputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/OutputStream.html) | +| | `java_io_Reader` | EqRef | [java.io.Reader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/Reader.html) | +| | `java_io_Writer` | EqRef | [java.io.Writer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/Writer.html) | +| `equality.java_io_charset` | `java_nio_charset_Charset` | Eq | [java.nio.charset.Charset](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html) | +| `equality.java_lang` | `java_lang_Boolean` | Eq | [java.lang.Boolean](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Boolean.html) | +| | `java_lang_Byte` | Eq | [java.lang.Byte](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Byte.html) | +| | `java_lang_Character` | Eq | [java.lang.Character](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html) | +| | `java_lang_Double` | Eq | [java.lang.Double](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Double.html) | +| | `java_lang_Enum` | Eq | [java.lang.Enum](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html) | +| | `java_lang_Float` | Eq | [java.lang.Float](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Float.html) | +| | `java_lang_Integer` | Eq | [java.lang.Integer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Integer.html) | +| | `java_lang_Short` | Eq | [java.lang.Short](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Short.html) | +| | `java_lang_Thread` | EqRef | [java.lang.Thread](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html) | +| | `java_lang_Throwable` | EqRef | [java.lang.Throwable](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Throwable.html) | +| | `java_lang_Void` | Eq | [java.lang.Void](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Void.html) | +| | `java_lang_Number` | Eq | [java.lang.Number](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Number.html) | +| `equality.java_math` | `java_math_BigInteger` | Eq | [java.math.BigInteger](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigInteger.html) | +| | `java_math_MathContext` | Eq | [java.math.MathContext](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/MathContext.html) | +| | `java_math_RoundingMode` | Eq | [java.math.RoundingMode](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/RoundingMode.html) | +| `equality.java_net` | `java_net_HttpCookie` | Eq | [java.net.HttpCookie](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html) | +| | `java_net_IDN` | EqRef | [java.net.IDN](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/IDN.html) | +| | `java_net_Inet4Address` | Eq | [java.net.Inet4Address](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Inet4Address.html) | +| | `java_net_Inet6Address` | Eq | [java.net.Inet6Address](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Inet6Address.html) | +| | `java_net_MulticastSocket` | EqRef | [java.net.MulticastSocket](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/MulticastSocket.html) | +| | `java_net_NetworkInterface` | Eq | [java.net.NetworkInterface](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/NetworkInterface.html) | +| | `java_net_ProtocolFamily` | Eq | [java.net.ProtocolFamily](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/ProtocolFamily.html) | +| | `java_net_ServerSocket` | EqRef | [java.net.ServerSocket](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/ServerSocket.html) | +| | `java_net_Socket` | EqRef | [java.net.Socket](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Socket.html) | +| | `java_net_SocketOption` | Eq | [java.net.SocketOption](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/SocketOption.html) | +| | `java_net_SocketOptions` | Eq | [java.net.SocketOptions](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/SocketOptions.html) | +| | `java_net_URI` | Eq | [java.net.URI](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URI.html) | +| | `java_net_URL` | Eq | [java.net.URL](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URL.html) | +| `equality.java_nio` | `java_nio_ByteBuffer` | Eq | [java.nio.ByteBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ByteBuffer.html) | +| | `java_nio_ByteOrder` | Eq | [java.nio.ByteOrder](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ByteOrder.html) | +| | `java_nio_DoubleBuffer` | Eq | [java.nio.DoubleBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/DoubleBuffer.html) | +| | `java_nio_FloatBuffer` | Eq | [java.nio.FloatBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/FloatBuffer.html) | +| | `java_nio_IntBuffer` | Eq | [java.nio.IntBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/IntBuffer.html) | +| | `java_nio_LongBuffer` | Eq | [java.nio.LongBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/LongBuffer.html) | +| | `java_nio_ShortBuffer` | Eq | [java.nio.ShortBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ShortBuffer.html) | +| `equality.java_nio_charset` | `java_nio_charset_Charset` | Eq | [java.nio.charset.Charset](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html) | +| `equality.java_nio_file` | `java_nio_file_Path` | Eq | [java.nio.file.Path](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html) | +| `equality.java_security` | `java_security_Permission` | Eq | [java.security.Permission](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/Permission.html) | +| | `java_security_Principal` | Eq | [java.security.Principal](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/Principal.html) | +| | `java_security_Timestamp` | Eq | [java.security.Timestamp](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/Timestamp.html) | +| `equality.java_sql` | `java_sql_SQLType ` | Eq | [java.sql.SQLType](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLType.html) | +| `equality.java_text` | `java_text_Collator` | Eq | [java.text.Collator](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/Collator.html) | +| | `java_text_Format` | Eq | [java.text.Format](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/Format.html) | +| `equality.java_time` | `java_time_DayOfWeek` | Eq | [java.time.DayOfWeek](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/DayOfWeek.html) | +| | `java_time_Duration` | Eq | [java.time.Duration](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Duration.html) | +| | `java_time_Instant` | Eq | [java.time.Instant](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Instant.html) | +| | `java_time_LocalDateTime` | Eq | [java.time.LocalDateTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalDateTime.html) | +| | `java_time_LocalDate` | Eq | [java.time.LocalDate](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalDate.html) | +| | `java_time_LocalTime` | Eq | [java.time.LocalTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalTime.html) | +| | `java_time_MonthDay` | Eq | [java.time.MonthDay](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/MonthDay.html) | +| | `java_time_Month` | Eq | [java.time.Month](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Month.html) | +| | `java_time_OffsetDateTime` | Eq | [java.time.OffsetDateTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/OffsetDateTime.html) | +| | `java_time_OffsetTime` | Eq | [java.time.OffsetTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/OffsetTime.html) | +| | `java_time_Period` | Eq | [java.time.Period](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Period.html) | +| | `java_time_YearMonth` | Eq | [java.time.YearMonth](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/YearMonth.html) | +| | `java_time_Year` | Eq | [java.time.Year](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Year.html) | +| | `java_time_ZoneId` | Eq | [java.time.ZoneId](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html) | +| | `java_time_ZoneOffset` | Eq | [java.time.ZoneOffset](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneOffset.html) | +| | `java_time_ZonedDateTime` | Eq | [java.time.ZonedDateTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZonedDateTime.html) | +| `equality.java_util` | `java_util_Date` | Eq | [java.util.Date](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Date.html) | +| | `java_util_List` | Eq | [java.util.List](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) | +| | `java_util_Locale_LanguageRange` | Eq | [java.util.Locale.LanguageRange](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Locale.LanguageRange.html) | +| | `java_util_Locale` | Eq | [java.util.Locale](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Locale.html) | +| | `java_util_Map` | Eq | [java.util.Map](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Map.html) | +| | `java_util_OptionalDouble` | Eq | [java.util.OptionalDouble](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/OptionalDouble.html) | +| | `java_util_OptionalInt` | Eq | [java.util.OptionalInt](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/OptionalInt.html) | +| | `java_util_OptionalLong` | Eq | [java.util.OptionalLong](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/OptionalLong.html) | +| | `java_util_Optional` | Eq | [java.util.Optional](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html) | +| | `java_util_Properties` | Eq | [java.util.Properties](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Properties.html) | +| | `java_util_Queue` | Eq | [java.util.Queue](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Queue.html) | +| | `java_util_Set` | Eq | [java.util.Set](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Set.html) | +| | `java_util_TimeZone` | Eq | [java.util.TimeZone](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TimeZone.html) | diff --git a/doc/example-ide-1k.png b/doc/example-ide-1k.png new file mode 100644 index 00000000..0774841e Binary files /dev/null and b/doc/example-ide-1k.png differ diff --git a/eq/src/main/scala/equality/CheckStrictEqualityBuild.scala b/eq/src/main/scala/equality/CheckStrictEqualityBuild.scala deleted file mode 100644 index 1f5a6bb6..00000000 --- a/eq/src/main/scala/equality/CheckStrictEqualityBuild.scala +++ /dev/null @@ -1,10 +0,0 @@ -package equality - -import scala.annotation.implicitNotFound -import scala.util.NotGiven - -private val msg = "the sources need to be compiled with -language:strictEquality" - -def checkStrictEqualityBuild()(using @implicitNotFound(msg) ev: NotGiven[CanEqual[Any, Any]]): Boolean = true - -private val enforce = checkStrictEqualityBuild() \ No newline at end of file diff --git a/eq/src/main/scala/equality/all/Exports.scala b/eq/src/main/scala/equality/all/Exports.scala deleted file mode 100644 index 532b5e17..00000000 --- a/eq/src/main/scala/equality/all/Exports.scala +++ /dev/null @@ -1,27 +0,0 @@ -package equality.all - -export equality.Eq -export equality.Eq.given - -export equality.scala_.{AnyNumber, AnyJavaNumber} -export equality.scala_.EqInstances.given -export equality.scala_collection.CollectionExtension.* -export equality.scala_collection.EqInstances.given -export equality.scala_concurrent.EqInstances.given -export equality.scala_concurrent_duration.EqInstances.given -export equality.scala_io.EqInstances.given -export equality.scala_math.EqInstances.given -export equality.scala_util.EqInstances.given - -export equality.java_io.EqInstances.given -export equality.java_lang.EqInstances.given -export equality.java_math.EqInstances.given -export equality.java_net.EqInstances.given -export equality.java_nio.EqInstances.given -export equality.java_nio_charset.EqInstances.given -export equality.java_nio_file.EqInstances.given -export equality.java_security.EqInstances.given -export equality.java_sql.EqInstances.given -export equality.java_text.EqInstances.given -export equality.java_time.EqInstances.given -export equality.java_util.EqInstances.given diff --git a/eq/src/main/scala/equality/java_io/EqInstances.scala b/eq/src/main/scala/equality/java_io/EqInstances.scala deleted file mode 100644 index d00d3fbd..00000000 --- a/eq/src/main/scala/equality/java_io/EqInstances.scala +++ /dev/null @@ -1,14 +0,0 @@ -package equality.java_io - -import equality.Eq - -export EqInstances.given - -object EqInstances: - given java_io_File: Eq[java.io.File] = Eq.assumed - given java_io_FileDescriptor: Eq[java.io.FileDescriptor] = Eq.assumed - given java_io_InputStream: Eq[java.io.InputStream] = Eq.assumed - given java_io_OutputStream: Eq[java.io.OutputStream] = Eq.assumed - given java_io_Reader: Eq[java.io.Reader] = Eq.assumed - given java_io_Writer: Eq[java.io.Writer] = Eq.assumed - \ No newline at end of file diff --git a/eq/src/main/scala/equality/java_security/EqInstances.scala b/eq/src/main/scala/equality/java_security/EqInstances.scala deleted file mode 100644 index 4479b0bf..00000000 --- a/eq/src/main/scala/equality/java_security/EqInstances.scala +++ /dev/null @@ -1,10 +0,0 @@ -package equality.java_security - -import equality.Eq - -export EqInstances.given - -object EqInstances: - given scala_security_Permission: Eq[java.security.Permission] = Eq.assumed - given scala_security_Principal: Eq[java.security.Principal] = Eq.assumed - given scala_security_Timestamp: Eq[java.security.Timestamp] = Eq.assumed diff --git a/eq/src/main/scala/equality/scala_concurrent/EqInstances.scala b/eq/src/main/scala/equality/scala_concurrent/EqInstances.scala deleted file mode 100644 index 71f103f8..00000000 --- a/eq/src/main/scala/equality/scala_concurrent/EqInstances.scala +++ /dev/null @@ -1,9 +0,0 @@ -package equality.scala_concurrent - -import equality.Eq - -export EqInstances.given - -object EqInstances: - given scala_concurrent_Future[T: Eq]: Eq[scala.concurrent.Future[T]] = Eq.assumed - given scala_concurrent_Promise[T: Eq]: Eq[scala.concurrent.Promise[T]] = Eq.assumed diff --git a/eq/src/main/scala/equality/universal/EqInstances.scala b/eq/src/main/scala/equality/universal/EqInstances.scala deleted file mode 100644 index cf63190e..00000000 --- a/eq/src/main/scala/equality/universal/EqInstances.scala +++ /dev/null @@ -1,9 +0,0 @@ -package equality.universal - -import equality.Eq - -export EqInstances.given - -object EqInstances: - given scala_Any: Eq[Any] = Eq.assumed - diff --git a/eq/src/test/scala/equality/TypeNameUtil.scala b/eq/src/test/scala/equality/TypeNameUtil.scala deleted file mode 100644 index 54dd8684..00000000 --- a/eq/src/test/scala/equality/TypeNameUtil.scala +++ /dev/null @@ -1,13 +0,0 @@ -package equality - -import scala.quoted.* - -object TypeNameUtil: - - inline def nameOf[A]: String = ${ nameOf[A] } - - def nameOf[A: Type](using Quotes): Expr[String] = - import quotes.reflect.* - - val typeRepr = TypeRepr.of[A] - Expr(typeRepr.show(using Printer.TypeReprShortCode)) \ No newline at end of file diff --git a/equality/src/main/scala/equality/Exports.scala b/equality/src/main/scala/equality/Exports.scala new file mode 100644 index 00000000..81136de7 --- /dev/null +++ b/equality/src/main/scala/equality/Exports.scala @@ -0,0 +1,31 @@ +package equality + +export equality.core.Eq +export equality.core.Eq.canEqualFromEq +export equality.core.Eq.eqFromAssumed + +export equality.core.EqRef +export equality.core.EqRef.Operators.* + +export equality.scala_.{AnyNumber, AnyJavaNumber} +export equality.scala_.Instances.given +export equality.scala_collection.Instances.given +export equality.scala_collection.CollectionExtension.* +export equality.scala_concurrent.Instances.given +export equality.scala_concurrent_duration.Instances.given +export equality.scala_io.Instances.given +export equality.scala_math.Instances.given +export equality.scala_util.Instances.given + +export equality.java_io.Instances.given +export equality.java_lang.Instances.given +export equality.java_math.Instances.given +export equality.java_net.Instances.given +export equality.java_nio.Instances.given +export equality.java_nio_charset.Instances.given +export equality.java_nio_file.Instances.given +export equality.java_security.Instances.given +export equality.java_sql.Instances.given +export equality.java_text.Instances.given +export equality.java_time.Instances.given +export equality.java_util.Instances.given diff --git a/equality/src/main/scala/equality/core/BuildCheck.scala b/equality/src/main/scala/equality/core/BuildCheck.scala new file mode 100644 index 00000000..6d4243e2 --- /dev/null +++ b/equality/src/main/scala/equality/core/BuildCheck.scala @@ -0,0 +1,10 @@ +package equality.core + +import scala.annotation.implicitNotFound +import scala.util.NotGiven + +private val message = "the sources need to be compiled with -language:strictEquality" + +private val _ = checkStrictEqualityBuild() + +def checkStrictEqualityBuild()(using @implicitNotFound(message) ev: NotGiven[CanEqual[Any, Any]]): Boolean = true diff --git a/eq/src/main/scala/equality/Eq.scala b/equality/src/main/scala/equality/core/Eq.scala similarity index 67% rename from eq/src/main/scala/equality/Eq.scala rename to equality/src/main/scala/equality/core/Eq.scala index 6cf5d527..bcc3eba2 100644 --- a/eq/src/main/scala/equality/Eq.scala +++ b/equality/src/main/scala/equality/core/Eq.scala @@ -1,46 +1,40 @@ -package equality +package equality.core import annotation.implicitNotFound -export Eq.given - -// Marker trait for unit tests -private[equality] trait EqTest +export Eq.eqFromAssumed +export Eq.canEqualFromEq /** - * Strict equality type class with automatic derivation for `Product` types. + * Value equality type class with automatic derivation for `Product` types. * * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#eq-type-class Library documentation]] - * @tparam T `Product` type + * @tparam T arbitrary type */ - @implicitNotFound("Values of types ${T} and ${T} cannot be compared with == or !=") sealed trait Eq[-T]: - /** - * For testing only - */ + /** For testing only. */ private[equality] val violations: Seq[String] = Nil object Eq: - given eq_from_assumed[A: Eq.assumed]: Eq[A] = Instance - given eq_CanEqual[T: Eq]: CanEqual[T, T] = CanEqual.derived - + given canEqualFromEq[T: Eq]: CanEqual[T, T] = CanEqual.derived + given eqFromAssumed[A: Eq.assumed]: Eq[A] = EqAny /** * Creates an Eq instance for an arbitrary type without verifying equality requirements. * * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#assuming-equality Library documentation]] + * @tparam T arbitrary type */ - object assumed extends assumed[Any]: - def derived[T]: assumed[T] = assumed + sealed trait assumed[-T] extends Eq[T] /** * Creates an Eq instance for an arbitrary type without verifying equality requirements. * * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#assuming-equality Library documentation]] - * @tparam T arbitrary type */ - sealed trait assumed[-T] extends Eq[T] + object assumed extends assumed[Any]: + def derived[T]: assumed[T] = assumed /** * Creates an Eq instance for a `Product` type while verifying equality requirements. @@ -51,7 +45,6 @@ object Eq: */ inline def derived[T]: Eq[T] = EqMacro.derived - /** * Identity function that requires an Eq type class instance for the passed argument. * @@ -60,14 +53,22 @@ object Eq: */ def apply[T: Eq](value: T): T = value - private object Instance extends Eq[Any] + private object EqAny extends Eq[Any] - private[equality] def apply[T]: Eq[T] = Instance + private[equality] def apply[T]: Eq[T] = EqAny - - // For testing only - private[equality] def apply[T](violationSeq: Seq[String]): Eq[T] = + /** For testing only. */ + private[equality] def apply[T](testViolations: Seq[String]): Eq[T] = new Eq[T]: - override val violations = violationSeq - -end Eq \ No newline at end of file + override val violations = testViolations + + object Universal: + + /** + * Enables universal equality. + * + * This is equivalent to the default behavior of `==` and `!=` operators in Scala. + */ + given scala_Any: Eq[Any] = Eq.assumed + end Universal +end Eq diff --git a/eq/src/main/scala/equality/EqMacro.scala b/equality/src/main/scala/equality/core/EqMacro.scala similarity index 86% rename from eq/src/main/scala/equality/EqMacro.scala rename to equality/src/main/scala/equality/core/EqMacro.scala index c953008f..428bec60 100644 --- a/eq/src/main/scala/equality/EqMacro.scala +++ b/equality/src/main/scala/equality/core/EqMacro.scala @@ -1,4 +1,4 @@ -package equality +package equality.core import scala.quoted.{Expr, Quotes, Type} @@ -14,11 +14,11 @@ private[equality] object EqMacro: val typeRepr = TypeRepr.of[T] val violations = EqReflection(typeRepr).violations - if typeRepr <:< TypeRepr.of[EqTest] then + if typeRepr <:< TypeRepr.of[ProductTest] then // Continue the compilation and store the violations in the type class instance for unit test evaluation '{ Eq[T](${ Expr(violations) }) } else violations foreach report.error '{ Eq[T] } -end EqMacro \ No newline at end of file +end EqMacro diff --git a/equality/src/main/scala/equality/core/EqRef.scala b/equality/src/main/scala/equality/core/EqRef.scala new file mode 100644 index 00000000..91376935 --- /dev/null +++ b/equality/src/main/scala/equality/core/EqRef.scala @@ -0,0 +1,83 @@ +package equality.core + +import scala.annotation.implicitNotFound + +export EqRef.Operators.* + +/** + * Reference equality type class with automatic derivation for final types. + * + * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#verifying-reference-equality Library documentation]] + * @tparam T arbitrary type + */ +@implicitNotFound("References of types ${T} and ${T} cannot be compared with eqRef or neRef") +sealed trait EqRef[-T] + +object EqRef: + + /** + * Creates an EqRef instance for an arbitrary type without verifying equality requirements. + * + * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#assuming-reference-equality Library documentation]] + * @tparam T arbitrary type + */ + sealed trait assumed[-T] extends EqRef[T] + + /** + * Creates an EqRef instance for an arbitrary type without verifying equality requirements. + * + * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#assuming-reference-equality documentation]] + */ + object assumed extends assumed[Any]: + def derived[T]: assumed[T] = assumed + + // FIXME - consider implementing a derivation mechanism + inline def derived[T]: EqRef[T] = EqRefAny + + object Operators: + extension[T <: AnyRef: EqRef] (value: T) + + /** + * Equality-safe alternative to the eq operator. + * + * @see [[https://github.com/antognini/type-safe-equality#verifying-reference-equality Library documentation]] + * @tparam U target type + * @param otherValue target value to be compared against + * @return true if the two references are equal, false otherwise + */ + inline def eqRef[U <: T: EqRef](otherValue: U): Boolean = value eq otherValue + + /** + * Equality-safe alternative to the ne operator. + * + * @see [[https://github.com/antognini/type-safe-equality/blob/main/README.md#verifying-reference-equality Library documentation]] + * @tparam U target type + * @param otherValue target value to be compared against + * @return true if the two references are not equal, false otherwise + */ + inline def neRef[U <: T: EqRef](otherValue: U): Boolean = value ne otherValue + end extension + end Operators + + /** + * Enables hybrid equality. + * + * This is equivalent to the default behavior of `==` and `!=` operators in Scala but + * disallowing comparison of values for unrelated or unsupported types. + */ + object Hybrid: + /** + * Creates an Eq instance equivalent to this EqRef instance. + * + * This has the following additional effects: + * - `==` and `!=` operators can also compare references if given EqRef instance is in scope for the compared type. + * - Eq derivation mechanism for product types supports fields with EqRef instances. + * + * @tparam T arbitrary type + * @return Eq instance equivalent to the this EqRef instance + */ + given eqRefToEq[T: EqRef]: Eq[T] = Eq.assumed + end Hybrid + + private object EqRefAny extends EqRef[Any] +end EqRef diff --git a/eq/src/main/scala/equality/EqReflection.scala b/equality/src/main/scala/equality/core/EqReflection.scala similarity index 64% rename from eq/src/main/scala/equality/EqReflection.scala rename to equality/src/main/scala/equality/core/EqReflection.scala index 562d3231..42db8dc5 100644 --- a/eq/src/main/scala/equality/EqReflection.scala +++ b/equality/src/main/scala/equality/core/EqReflection.scala @@ -1,28 +1,26 @@ -package equality - -import equality.TypeParameterSupport.* +package equality.core import scala.quoted.{Expr, Quotes, Type} +import TypeSupport.{extractTypeParameters, wildcardTypeParameter} private[equality] class EqReflection(using val quotes: Quotes)(typeRepr: quotes.reflect.TypeRepr): - import quotes.reflect.* lazy val violations: Seq[String] = if isProduct then val summonErrors = fields - .filterNot(_.canSummonEq) - .map(_.typeName) - .distinct - .map(replaceAllTypeParametersWithWildCards) - .map: typeName => - s"Values of types $typeName and $typeName cannot be compared with == or !=" + .filterNot(_.canSummonEq) + .map(_.typeName) + .distinct + .map(wildcardTypeParameters) + .map: typeName => + s"Values of types $typeName and $typeName cannot be compared with == or !=" val contextBoundErrors = fields.flatMap: field => field.typeParameters.collect: - // double-check if the extracted type parameter is really declared + // Double-check if the extracted type parameter is really declared case typeParameter if typeParameters.contains(typeParameter) && !hasImplicitEqParameter(typeParameter) => typeParameter .distinct .map: typeParameter => @@ -32,11 +30,13 @@ private[equality] class EqReflection(using val quotes: Quotes)(typeRepr: quotes. else Seq(s"$typeName is not a product type") - private lazy val typeName = nameFor(typeRepr) + private lazy val fields: Seq[Field] = typeRepr.typeSymbol.caseFields.map: fieldSymbol => - typeRepr.dealias.memberType(fieldSymbol) + typeRepr + .dealias + .memberType(fieldSymbol) .map(Field.apply) private lazy val implicitParamTypes: Seq[ImplicitParamType] = @@ -69,38 +69,22 @@ private[equality] class EqReflection(using val quotes: Quotes)(typeRepr: quotes. case '[Product] => true case _ => false - private def nameFor(typeRepr: TypeRepr) = - typeRepr.show(using Printer.TypeReprShortCode) - private def hasImplicitEqParameter(typeName: String) = implicitParamTypes.exists(_.isEqForType(typeName)) - private def replaceAllTypeParametersWithWildCards(typeName: String): String = - typeParameters.fold(typeName)( - (tpe, typeParameter) => tpe.replaceTypeParameterWithWildcard(typeParameter) - ) + private def wildcardTypeParameters(typeName: String): String = + typeParameters.fold(typeName): + (tpe, typeParameter) => tpe.wildcardTypeParameter(typeParameter) - private case class Field( - typeRepr: TypeRepr - )(using Quotes): - - lazy val typeName = nameFor(typeRepr) - - lazy val canSummonEq: Boolean = - typeRepr.asType match - case '[f] => Expr.summon[Eq[f]].nonEmpty - - lazy val typeParameters: Seq[String] = extractTypeParams(typeName) - end Field + private def nameFor(typeRepr: TypeRepr) = + typeRepr.show(using Printer.TypeReprShortCode) + private case class Field(typeRepr: TypeRepr)(using Quotes): + lazy val typeName: String = nameFor(typeRepr) + lazy val typeParameters: Seq[String] = typeName.extractTypeParameters + lazy val canSummonEq: Boolean = typeRepr.asType match + case '[f] => Expr.summon[Eq[f]].nonEmpty - private case class ImplicitParamType( - typeRepr: TypeRepr, - ): + private case class ImplicitParamType(typeRepr: TypeRepr): lazy val typeName = nameFor(typeRepr) - - def isEqForType(tpe: String): Boolean = - typeName.endsWith(s"Eq[$tpe]") - - end ImplicitParamType -end EqReflection + def isEqForType(tpe: String) = typeName.endsWith(s"Eq[$tpe]") diff --git a/equality/src/main/scala/equality/core/ProductTest.scala b/equality/src/main/scala/equality/core/ProductTest.scala new file mode 100644 index 00000000..2e2794ad --- /dev/null +++ b/equality/src/main/scala/equality/core/ProductTest.scala @@ -0,0 +1,4 @@ +package equality.core + +/** For testing only. */ +private[equality] trait ProductTest diff --git a/eq/src/main/scala/equality/TypeParameterSupport.scala b/equality/src/main/scala/equality/core/TypeSupport.scala similarity index 64% rename from eq/src/main/scala/equality/TypeParameterSupport.scala rename to equality/src/main/scala/equality/core/TypeSupport.scala index fbf5d371..ed271085 100644 --- a/eq/src/main/scala/equality/TypeParameterSupport.scala +++ b/equality/src/main/scala/equality/core/TypeSupport.scala @@ -1,14 +1,14 @@ -package equality +package equality.core -private[equality] object TypeParameterSupport: +private[equality] object TypeSupport: private val separator = """\s*[,\[(\])]+\s*""".r private val replacement = "$1?$2" extension (tpe: String) - def extractTypeParams: Seq[String] = + def extractTypeParameters: Seq[String] = separator.split(tpe).distinct.filter(!_.contains(".")).toSeq - def replaceTypeParameterWithWildcard(typeParameter: String): String = + def wildcardTypeParameter(typeParameter: String): String = val pattern = s"($separator)$typeParameter($separator)".r pattern.replaceAllIn(pattern.replaceAllIn(tpe, replacement), replacement) \ No newline at end of file diff --git a/equality/src/main/scala/equality/hybrid/Exports.scala b/equality/src/main/scala/equality/hybrid/Exports.scala new file mode 100644 index 00000000..da993e28 --- /dev/null +++ b/equality/src/main/scala/equality/hybrid/Exports.scala @@ -0,0 +1,3 @@ +package equality.hybrid + +export equality.core.EqRef.Hybrid.given diff --git a/equality/src/main/scala/equality/java_io/Instances.scala b/equality/src/main/scala/equality/java_io/Instances.scala new file mode 100644 index 00000000..04bf8087 --- /dev/null +++ b/equality/src/main/scala/equality/java_io/Instances.scala @@ -0,0 +1,14 @@ +package equality.java_io + +import equality.core.{Eq, EqRef} + +export Instances.given + +object Instances: + given java_io_File: Eq[java.io.File] = Eq.assumed + given java_io_FileDescriptor: Eq[java.io.FileDescriptor] = Eq.assumed + given java_io_InputStream: EqRef[java.io.InputStream] = EqRef.assumed + given java_io_OutputStream: EqRef[java.io.OutputStream] = EqRef.assumed + given java_io_Reader: EqRef[java.io.Reader] = EqRef.assumed + given java_io_Writer: EqRef[java.io.Writer] = EqRef.assumed + diff --git a/eq/src/main/scala/equality/java_lang/EqInstances.scala b/equality/src/main/scala/equality/java_lang/Instances.scala similarity index 72% rename from eq/src/main/scala/equality/java_lang/EqInstances.scala rename to equality/src/main/scala/equality/java_lang/Instances.scala index d7457127..3188c903 100644 --- a/eq/src/main/scala/equality/java_lang/EqInstances.scala +++ b/equality/src/main/scala/equality/java_lang/Instances.scala @@ -1,10 +1,10 @@ package equality.java_lang -import equality.Eq +import equality.core.{Eq, EqRef} -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_lang_Boolean: Eq[java.lang.Boolean] = Eq.assumed given java_lang_Byte: Eq[java.lang.Byte] = Eq.assumed given java_lang_Character: Eq[java.lang.Character] = Eq.assumed @@ -15,6 +15,5 @@ object EqInstances: given java_lang_Long: Eq[java.lang.Long] = Eq.assumed given java_lang_Number: Eq[java.lang.Number] = Eq.assumed given java_lang_Short: Eq[java.lang.Short] = Eq.assumed - given java_lang_Thread: Eq[java.lang.Thread] = Eq.assumed - given java_lang_Throwable: Eq[java.lang.Throwable] = Eq.assumed - given java_lang_Void: Eq[java.lang.Void] = Eq.assumed + given java_lang_Thread: EqRef[java.lang.Thread] = EqRef.assumed + given java_lang_Throwable: EqRef[java.lang.Throwable] = EqRef.assumed diff --git a/eq/src/main/scala/equality/java_math/EqInstances.scala b/equality/src/main/scala/equality/java_math/Instances.scala similarity index 83% rename from eq/src/main/scala/equality/java_math/EqInstances.scala rename to equality/src/main/scala/equality/java_math/Instances.scala index 64f741ab..a2102350 100644 --- a/eq/src/main/scala/equality/java_math/EqInstances.scala +++ b/equality/src/main/scala/equality/java_math/Instances.scala @@ -1,10 +1,10 @@ package equality.java_math -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: // java_math_BigDecimal is intentionally omitted because it has a bug in equals() given java_math_BigInteger: Eq[java.math.BigInteger] = Eq.assumed given java_math_MathContext: Eq[java.math.MathContext] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_net/EqInstances.scala b/equality/src/main/scala/equality/java_net/Instances.scala similarity index 56% rename from eq/src/main/scala/equality/java_net/EqInstances.scala rename to equality/src/main/scala/equality/java_net/Instances.scala index 8d52d431..f3d188a8 100644 --- a/eq/src/main/scala/equality/java_net/EqInstances.scala +++ b/equality/src/main/scala/equality/java_net/Instances.scala @@ -1,19 +1,20 @@ package equality.java_net -import equality.Eq +import equality.core.{Eq, EqRef} -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_net_HttpCookie: Eq[java.net.HttpCookie] = Eq.assumed - given java_net_IDN: Eq[java.net.IDN] = Eq.assumed + given java_net_IDN: EqRef[java.net.IDN] = EqRef.assumed given java_net_Inet4Address: Eq[java.net.Inet4Address] = Eq.assumed given java_net_Inet6Address: Eq[java.net.Inet6Address] = Eq.assumed - given java_net_MulticastSocket: Eq[java.net.MulticastSocket] = Eq.assumed + given java_net_MulticastSocket: EqRef[java.net.MulticastSocket] = EqRef.assumed given java_net_NetworkInterface: Eq[java.net.NetworkInterface] = Eq.assumed given java_net_ProtocolFamily: Eq[java.net.ProtocolFamily] = Eq.assumed - given java_net_ServerSocket: Eq[java.net.ServerSocket] = Eq.assumed - given java_net_Socket: Eq[java.net.Socket] = Eq.assumed + given java_net_ServerSocket: EqRef[java.net.ServerSocket] = EqRef.assumed + given java_net_Socket: EqRef[java.net.Socket] = EqRef.assumed + given java_net_SocketOption[A: Eq]: Eq[java.net.SocketOption[A]] = Eq.assumed given java_net_SocketOptions: Eq[java.net.SocketOptions] = Eq.assumed given java_net_URI: Eq[java.net.URI] = Eq.assumed given java_net_URL: Eq[java.net.URL] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_nio/EqInstances.scala b/equality/src/main/scala/equality/java_nio/Instances.scala similarity index 88% rename from eq/src/main/scala/equality/java_nio/EqInstances.scala rename to equality/src/main/scala/equality/java_nio/Instances.scala index 1b12207d..fb5ccf13 100644 --- a/eq/src/main/scala/equality/java_nio/EqInstances.scala +++ b/equality/src/main/scala/equality/java_nio/Instances.scala @@ -1,10 +1,10 @@ package equality.java_nio -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_nio_ByteBuffer: Eq[java.nio.ByteBuffer] = Eq.assumed given java_nio_ByteOrder: Eq[java.nio.ByteOrder] = Eq.assumed given java_nio_DoubleBuffer: Eq[java.nio.DoubleBuffer] = Eq.assumed @@ -12,4 +12,3 @@ object EqInstances: given java_nio_IntBuffer: Eq[java.nio.IntBuffer] = Eq.assumed given java_nio_LongBuffer: Eq[java.nio.LongBuffer] = Eq.assumed given java_nio_ShortBuffer: Eq[java.nio.ShortBuffer] = Eq.assumed - \ No newline at end of file diff --git a/eq/src/main/scala/equality/java_nio_charset/EqInstances.scala b/equality/src/main/scala/equality/java_nio_charset/Instances.scala similarity index 63% rename from eq/src/main/scala/equality/java_nio_charset/EqInstances.scala rename to equality/src/main/scala/equality/java_nio_charset/Instances.scala index 8550bbf8..4f67adaf 100644 --- a/eq/src/main/scala/equality/java_nio_charset/EqInstances.scala +++ b/equality/src/main/scala/equality/java_nio_charset/Instances.scala @@ -1,8 +1,8 @@ package equality.java_nio_charset -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_nio_charset_CharSet: Eq[java.nio.charset.Charset] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_nio_file/EqInstances.scala b/equality/src/main/scala/equality/java_nio_file/Instances.scala similarity index 60% rename from eq/src/main/scala/equality/java_nio_file/EqInstances.scala rename to equality/src/main/scala/equality/java_nio_file/Instances.scala index 5081e411..64074f7f 100644 --- a/eq/src/main/scala/equality/java_nio_file/EqInstances.scala +++ b/equality/src/main/scala/equality/java_nio_file/Instances.scala @@ -1,8 +1,8 @@ package equality.java_nio_file -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_nio_file_Path: Eq[java.nio.file.Path] = Eq.assumed diff --git a/equality/src/main/scala/equality/java_security/Instances.scala b/equality/src/main/scala/equality/java_security/Instances.scala new file mode 100644 index 00000000..df4ba78f --- /dev/null +++ b/equality/src/main/scala/equality/java_security/Instances.scala @@ -0,0 +1,10 @@ +package equality.java_security + +import equality.core.{Eq, EqRef} + +export Instances.given + +object Instances: + given java_security_Permission: Eq[java.security.Permission] = Eq.assumed + given java_security_Principal: Eq[java.security.Principal] = Eq.assumed + given java_security_Timestamp: Eq[java.security.Timestamp] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_sql/EqInstances.scala b/equality/src/main/scala/equality/java_sql/Instances.scala similarity index 57% rename from eq/src/main/scala/equality/java_sql/EqInstances.scala rename to equality/src/main/scala/equality/java_sql/Instances.scala index 7251c176..08abdf5f 100644 --- a/eq/src/main/scala/equality/java_sql/EqInstances.scala +++ b/equality/src/main/scala/equality/java_sql/Instances.scala @@ -1,8 +1,8 @@ package equality.java_sql -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_sql_SQLType: Eq[java.sql.SQLType] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_text/EqInstances.scala b/equality/src/main/scala/equality/java_text/Instances.scala similarity index 70% rename from eq/src/main/scala/equality/java_text/EqInstances.scala rename to equality/src/main/scala/equality/java_text/Instances.scala index 92984d3d..ab18b62c 100644 --- a/eq/src/main/scala/equality/java_text/EqInstances.scala +++ b/equality/src/main/scala/equality/java_text/Instances.scala @@ -1,9 +1,9 @@ package equality.java_text -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_text_Collator: Eq[java.text.Collator] = Eq.assumed given java_text_Format: Eq[java.text.Format] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_time/EqInstances.scala b/equality/src/main/scala/equality/java_time/Instances.scala similarity index 94% rename from eq/src/main/scala/equality/java_time/EqInstances.scala rename to equality/src/main/scala/equality/java_time/Instances.scala index 8272fcd0..ad60b943 100644 --- a/eq/src/main/scala/equality/java_time/EqInstances.scala +++ b/equality/src/main/scala/equality/java_time/Instances.scala @@ -1,10 +1,10 @@ package equality.java_time -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_time_DayOfWeek: Eq[java.time.DayOfWeek] = Eq.assumed given java_time_Duration: Eq[java.time.Duration] = Eq.assumed given java_time_Instant: Eq[java.time.Instant] = Eq.assumed diff --git a/eq/src/main/scala/equality/java_util/EqInstances.scala b/equality/src/main/scala/equality/java_util/Instances.scala similarity index 93% rename from eq/src/main/scala/equality/java_util/EqInstances.scala rename to equality/src/main/scala/equality/java_util/Instances.scala index a2326d18..89bb3c91 100644 --- a/eq/src/main/scala/equality/java_util/EqInstances.scala +++ b/equality/src/main/scala/equality/java_util/Instances.scala @@ -1,10 +1,10 @@ package equality.java_util -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given java_util_Date: Eq[java.util.Date] = Eq.assumed given java_util_List[E: Eq]: Eq[java.util.List[E]] = Eq.assumed given java_util_Locale: Eq[java.util.Locale] = Eq.assumed diff --git a/eq/src/main/scala/equality/scala_/EqInstances.scala b/equality/src/main/scala/equality/scala_/Instances.scala similarity index 99% rename from eq/src/main/scala/equality/scala_/EqInstances.scala rename to equality/src/main/scala/equality/scala_/Instances.scala index f7a03bf6..47237b85 100644 --- a/eq/src/main/scala/equality/scala_/EqInstances.scala +++ b/equality/src/main/scala/equality/scala_/Instances.scala @@ -1,8 +1,8 @@ package equality.scala_ -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given type AnyNumber = Byte | Char | Short | Int | Long | BigInt | Float | Double | BigDecimal @@ -10,7 +10,7 @@ type AnyJavaNumber = java.lang.Byte | java.lang.Character | java.lang.Shor java.lang.Integer | java.lang.Long | java.math.BigInteger | java.lang.Float | java.lang.Double | java.math.BigDecimal -object EqInstances: +object Instances: given scala_AnyNumber: Eq[AnyNumber] = Eq.assumed given scala_AnyJavaNumber: Eq[AnyJavaNumber] = Eq.assumed diff --git a/eq/src/main/scala/equality/scala_collection/CollectionExtension.scala b/equality/src/main/scala/equality/scala_collection/CollectionExtension.scala similarity index 100% rename from eq/src/main/scala/equality/scala_collection/CollectionExtension.scala rename to equality/src/main/scala/equality/scala_collection/CollectionExtension.scala diff --git a/eq/src/main/scala/equality/scala_collection/CollectionExtensionIterableOnce.scala b/equality/src/main/scala/equality/scala_collection/CollectionExtensionIterableOnce.scala similarity index 90% rename from eq/src/main/scala/equality/scala_collection/CollectionExtensionIterableOnce.scala rename to equality/src/main/scala/equality/scala_collection/CollectionExtensionIterableOnce.scala index 93c38e09..0fae2cfe 100644 --- a/eq/src/main/scala/equality/scala_collection/CollectionExtensionIterableOnce.scala +++ b/equality/src/main/scala/equality/scala_collection/CollectionExtensionIterableOnce.scala @@ -1,6 +1,6 @@ package equality.scala_collection -import equality.Eq +import equality.core.Eq trait CollectionExtensionIterableOnce: extension[A, I <: collection.IterableOnce[A]] (iterableOnce: I) @@ -15,4 +15,4 @@ trait CollectionExtensionIterableOnce: (using Eq[B]): Boolean = iterableOnce.sameElements(that) end extension -end CollectionExtensionIterableOnce \ No newline at end of file +end CollectionExtensionIterableOnce diff --git a/eq/src/main/scala/equality/scala_collection/CollectionExtensionIterator.scala b/equality/src/main/scala/equality/scala_collection/CollectionExtensionIterator.scala similarity index 80% rename from eq/src/main/scala/equality/scala_collection/CollectionExtensionIterator.scala rename to equality/src/main/scala/equality/scala_collection/CollectionExtensionIterator.scala index 88b90590..7451d9bd 100644 --- a/eq/src/main/scala/equality/scala_collection/CollectionExtensionIterator.scala +++ b/equality/src/main/scala/equality/scala_collection/CollectionExtensionIterator.scala @@ -1,17 +1,17 @@ package equality.scala_collection -import equality.Eq +import equality.core.Eq trait CollectionExtensionIterator: - extension[A, S <: collection.Iterator[A]] (iterator: S) + extension[A, I <: collection.Iterator[A]] (iterator: I) /** * Equality-safe alternative to the contains() method. * * @see [[https://github.com/antognini/type-safe-equality/tree/main#collection-extensions Library documentation]] */ - inline def contains_eq[B >: A](x: B) - (using Eq[B]): Boolean = iterator.contains(x) + inline def contains_eq[B >: A](elem: B) + (using Eq[B]): Boolean = iterator.contains(elem) /** @@ -19,8 +19,8 @@ trait CollectionExtensionIterator: * * @see [[https://github.com/antognini/type-safe-equality/tree/main#collection-extensions Library documentation]] */ - inline def indexOf_eq[B >: A](x: B) - (using Eq[B]): Int = iterator.indexOf(x) + inline def indexOf_eq[B >: A](elem: B) + (using Eq[B]): Int = iterator.indexOf(elem) /** * Equality-safe alternative to the sameElements() method. @@ -34,15 +34,15 @@ trait CollectionExtensionIterator: end CollectionExtensionIterator trait CollectionExtensionIteratorOverload: - extension[A, S <: collection.Iterator[A]] (iterator: S) + extension[A, I <: collection.Iterator[A]] (iterator: I) /** * Equality-safe alternative to the indexOf method. * * @see [[https://github.com/antognini/type-safe-equality/tree/main#collection-extensions Library documentation]] */ - inline def indexOf_eq[B >: A](x: B, from: Int) - (using Eq[B]): Int = iterator.indexOf(x, from) + inline def indexOf_eq[B >: A](elem: B, from: Int) + (using Eq[B]): Int = iterator.indexOf(elem, from) end extension end CollectionExtensionIteratorOverload diff --git a/eq/src/main/scala/equality/scala_collection/CollectionExtensionSeq.scala b/equality/src/main/scala/equality/scala_collection/CollectionExtensionSeq.scala similarity index 97% rename from eq/src/main/scala/equality/scala_collection/CollectionExtensionSeq.scala rename to equality/src/main/scala/equality/scala_collection/CollectionExtensionSeq.scala index 3ca7228b..749d886f 100644 --- a/eq/src/main/scala/equality/scala_collection/CollectionExtensionSeq.scala +++ b/equality/src/main/scala/equality/scala_collection/CollectionExtensionSeq.scala @@ -1,6 +1,6 @@ package equality.scala_collection -import equality.Eq +import equality.core.Eq import scala.collection.Searching.SearchResult import scala.collection.mutable @@ -13,8 +13,8 @@ trait CollectionExtensionSeq: * * @see [[https://github.com/antognini/type-safe-equality/tree/main#collection-extensions Library documentation]] */ - inline def contains_eq[B >: A](x: B) - (using Eq[B]): Boolean = seq.contains(x) + inline def contains_eq[B >: A](elem: B) + (using Eq[B]): Boolean = seq.contains(elem) /** * Equality-safe alternative to the containsSlice() method. @@ -37,8 +37,8 @@ trait CollectionExtensionSeq: * * @see [[https://github.com/antognini/type-safe-equality/tree/main#collection-extensions Library documentation]] */ - inline def indexOf_eq[B >: A](x: B) - (using Eq[B]): Int = seq.indexOf(x) + inline def indexOf_eq[B >: A](elem: B) + (using Eq[B]): Int = seq.indexOf(elem) /** * Equality-safe alternative to the indexOfSlice() method. @@ -99,8 +99,8 @@ trait CollectionExtensionSeqOverload: * * @see [[https://github.com/antognini/type-safe-equality/tree/main#collection-extensions Library documentation]] */ - inline def indexOf_eq[B >: A](x: B, from: Int) - (using Eq[B]): Int = seq.indexOf(x, from) + inline def indexOf_eq[B >: A](elem: B, from: Int) + (using Eq[B]): Int = seq.indexOf(elem, from) /** * Equality-safe alternative to the indexOfSlice() method. diff --git a/eq/src/main/scala/equality/scala_collection/EqInstances.scala b/equality/src/main/scala/equality/scala_collection/Instances.scala similarity index 81% rename from eq/src/main/scala/equality/scala_collection/EqInstances.scala rename to equality/src/main/scala/equality/scala_collection/Instances.scala index e3971ac7..efa52c77 100644 --- a/eq/src/main/scala/equality/scala_collection/EqInstances.scala +++ b/equality/src/main/scala/equality/scala_collection/Instances.scala @@ -1,10 +1,10 @@ package equality.scala_collection -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given scala_collection_Map[A: Eq, B: Eq]: Eq[scala.collection.Map[A, B]] = Eq.assumed given scala_collection_Seq[A: Eq]: Eq[scala.collection.Seq[A]] = Eq.assumed given scala_collection_Set[A: Eq]: Eq[scala.collection.Set[A]] = Eq.assumed diff --git a/equality/src/main/scala/equality/scala_concurrent/Instances.scala b/equality/src/main/scala/equality/scala_concurrent/Instances.scala new file mode 100644 index 00000000..abf948f4 --- /dev/null +++ b/equality/src/main/scala/equality/scala_concurrent/Instances.scala @@ -0,0 +1,9 @@ +package equality.scala_concurrent + +import equality.core.{Eq, EqRef} + +export Instances.given + +object Instances: + given scala_concurrent_Future[T: Eq]: EqRef[scala.concurrent.Future[T]] = EqRef.assumed + given scala_concurrent_Promise[T: Eq]: EqRef[scala.concurrent.Promise[T]] = EqRef.assumed diff --git a/eq/src/main/scala/equality/scala_concurrent_duration/EqInstances.scala b/equality/src/main/scala/equality/scala_concurrent_duration/Instances.scala similarity index 78% rename from eq/src/main/scala/equality/scala_concurrent_duration/EqInstances.scala rename to equality/src/main/scala/equality/scala_concurrent_duration/Instances.scala index 68966266..16909de9 100644 --- a/eq/src/main/scala/equality/scala_concurrent_duration/EqInstances.scala +++ b/equality/src/main/scala/equality/scala_concurrent_duration/Instances.scala @@ -1,9 +1,9 @@ package equality.scala_concurrent_duration -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given scala_concurrent_duration_Deadline: Eq[scala.concurrent.duration.Deadline] = Eq.assumed given scala_concurrent_duration_Duration: Eq[scala.concurrent.duration.Duration] = Eq.assumed diff --git a/eq/src/main/scala/equality/scala_io/EqInstances.scala b/equality/src/main/scala/equality/scala_io/Instances.scala similarity index 56% rename from eq/src/main/scala/equality/scala_io/EqInstances.scala rename to equality/src/main/scala/equality/scala_io/Instances.scala index 891817f6..5fe0cbe9 100644 --- a/eq/src/main/scala/equality/scala_io/EqInstances.scala +++ b/equality/src/main/scala/equality/scala_io/Instances.scala @@ -1,8 +1,8 @@ package equality.scala_io -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given scala_io_Codec: Eq[scala.io.Codec] = Eq.assumed diff --git a/eq/src/main/scala/equality/scala_math/EqInstances.scala b/equality/src/main/scala/equality/scala_math/Instances.scala similarity index 71% rename from eq/src/main/scala/equality/scala_math/EqInstances.scala rename to equality/src/main/scala/equality/scala_math/Instances.scala index 06eb1766..1feee888 100644 --- a/eq/src/main/scala/equality/scala_math/EqInstances.scala +++ b/equality/src/main/scala/equality/scala_math/Instances.scala @@ -1,9 +1,9 @@ package equality.scala_math -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given scala_math_BigDecimal: Eq[scala.math.BigDecimal] = Eq.assumed given scala_math_BigInt: Eq[scala.math.BigInt] = Eq.assumed diff --git a/eq/src/main/scala/equality/scala_util/EqInstances.scala b/equality/src/main/scala/equality/scala_util/Instances.scala similarity index 73% rename from eq/src/main/scala/equality/scala_util/EqInstances.scala rename to equality/src/main/scala/equality/scala_util/Instances.scala index 3eb8dc0d..cade9061 100644 --- a/eq/src/main/scala/equality/scala_util/EqInstances.scala +++ b/equality/src/main/scala/equality/scala_util/Instances.scala @@ -1,9 +1,9 @@ package equality.scala_util -import equality.Eq +import equality.core.Eq -export EqInstances.given +export Instances.given -object EqInstances: +object Instances: given scala_util_Either[A: Eq, B: Eq]: Eq[scala.util.Either[A, B]] = Eq.assumed given scala_util_Try[T: Eq]: Eq[scala.util.Try[T]] = Eq.assumed diff --git a/equality/src/main/scala/equality/universal/Exports.scala b/equality/src/main/scala/equality/universal/Exports.scala new file mode 100644 index 00000000..d408cad9 --- /dev/null +++ b/equality/src/main/scala/equality/universal/Exports.scala @@ -0,0 +1,3 @@ +package equality.universal + +export equality.core.Eq.Universal.given diff --git a/eq/src/test/scala/equality/DryRunTest.scala b/equality/src/test/scala/equality/DryRunTest.scala similarity index 64% rename from eq/src/test/scala/equality/DryRunTest.scala rename to equality/src/test/scala/equality/DryRunTest.scala index df0015e5..a58984fc 100644 --- a/eq/src/test/scala/equality/DryRunTest.scala +++ b/equality/src/test/scala/equality/DryRunTest.scala @@ -1,60 +1,53 @@ package equality -import equality.all.{*, given} +import equality.core.{*, given} import equality.TypeNameUtil.nameOf import org.scalatest.freespec.AnyFreeSpec class DryRunTest extends AnyFreeSpec: - "" - { "GenericClass" - { - class GenericClass extends EqTest derives Eq + class GenericClass extends ProductTest derives Eq inspect[GenericClass]( "GenericClass".notProduct ) } - s"Plain0" - { + "Plain0" - { case class Plain0() - - case class TestPlain0(x: Plain0) extends EqTest derives Eq + case class TestPlain0(x: Plain0) extends ProductTest derives Eq inspect[TestPlain0]( "Plain0".cannotCompare ) - s"Curried" - { - case class Curried[A](x: Int)(y: A, z: Plain0) extends EqTest derives Eq + "Curried" - { + case class Curried[A](x: Int)(y: A, z: Plain0) extends ProductTest derives Eq inspect[Curried[Int]]() } } - "Derived0" - { case class Derived0() derives Eq - - case class TestDerived0(x: Derived0) extends EqTest derives Eq + case class TestDerived0(x: Derived0) extends ProductTest derives Eq inspect[TestDerived0]() } "Plain1" - { case class Plain1[A](a: A) - - case class TestPlain1A(x: Plain1[Int]) extends EqTest derives Eq - case class TestPlain1B[A: Eq](x: Plain1[A]) extends EqTest derives Eq - case class TestPlain1C[A](x: Plain1[A]) extends EqTest derives Eq + case class TestPlain1A(x: Plain1[Int]) extends ProductTest derives Eq + case class TestPlain1B[A: Eq](x: Plain1[A]) extends ProductTest derives Eq + case class TestPlain1C[A](x: Plain1[A]) extends ProductTest derives Eq inspect[TestPlain1A]( "Plain1[Int]".cannotCompare ) - inspect[TestPlain1B[Int]]( "Plain1[?]".cannotCompare ) - inspect[TestPlain1C[Int]]( "Plain1[?]".cannotCompare, "A".noContextBound @@ -63,15 +56,12 @@ class DryRunTest extends AnyFreeSpec: "Derived1" - { case class Derived1[A: Eq](a: A) derives Eq - - case class TestDerived1A(x: Derived1[Int]) extends EqTest derives Eq - case class TestDerived1B[A: Eq](x: Derived1[A]) extends EqTest derives Eq - case class TestDerived1C[A](x: Derived1[A]) extends EqTest derives Eq + case class TestDerived1A(x: Derived1[Int]) extends ProductTest derives Eq + case class TestDerived1B[A: Eq](x: Derived1[A]) extends ProductTest derives Eq + case class TestDerived1C[A](x: Derived1[A]) extends ProductTest derives Eq inspect[TestDerived1A]() - inspect[TestDerived1B[Int]]() - inspect[TestDerived1C[Int]]( "A".noContextBound ) @@ -79,27 +69,23 @@ class DryRunTest extends AnyFreeSpec: "Plain2" - { case class Plain2[A, B](a: A, b: B) - - case class TestPlain2A(x: Plain2[Int, String]) extends EqTest derives Eq - case class TestPlain2B[A: Eq, B: Eq](x: Plain2[A, String], y: Plain2[Int, B]) extends EqTest derives Eq - case class TestPlain2C[A, B: Eq](x: Plain2[A, String], y: Plain2[Int, B]) extends EqTest derives Eq - case class TestPlain2D[A, B](x: Plain2[A, String], y: Plain2[Int, B]) extends EqTest derives Eq + case class TestPlain2A(x: Plain2[Int, String]) extends ProductTest derives Eq + case class TestPlain2B[A: Eq, B: Eq](x: Plain2[A, String], y: Plain2[Int, B]) extends ProductTest derives Eq + case class TestPlain2C[A, B: Eq](x: Plain2[A, String], y: Plain2[Int, B]) extends ProductTest derives Eq + case class TestPlain2D[A, B](x: Plain2[A, String], y: Plain2[Int, B]) extends ProductTest derives Eq inspect[TestPlain2A]( "Plain2[Int, String]".cannotCompare ) - inspect[TestPlain2B[Int, String]]( "Plain2[?, String]".cannotCompare, "Plain2[Int, ?]".cannotCompare ) - inspect[TestPlain2C[Int, String]]( "Plain2[?, String]".cannotCompare, "Plain2[Int, ?]".cannotCompare, "A".noContextBound ) - inspect[TestPlain2D[Int, String]]( "Plain2[?, String]".cannotCompare, "Plain2[Int, ?]".cannotCompare, @@ -107,47 +93,35 @@ class DryRunTest extends AnyFreeSpec: "B".noContextBound ) } - "Derived2" - { case class Derived2[A: Eq, B: Eq](a: A, b: B) derives Eq - - case class TestDerived2A(x: Derived2[Int, String]) extends EqTest derives Eq - case class TestDerived2B[A: Eq, B: Eq](x: Derived2[A, String], y: Derived2[Int, B]) extends EqTest derives Eq - case class TestDerived2C[A, B: Eq](x: Derived2[A, String], y: Derived2[Int, B]) extends EqTest derives Eq - case class TestDerived2D[A, B](x: Derived2[A, String], y: Derived2[Int, B]) extends EqTest derives Eq + case class TestDerived2A(x: Derived2[Int, String]) extends ProductTest derives Eq + case class TestDerived2B[A: Eq, B: Eq](x: Derived2[A, String], y: Derived2[Int, B]) extends ProductTest derives Eq + case class TestDerived2C[A, B: Eq](x: Derived2[A, String], y: Derived2[Int, B]) extends ProductTest derives Eq + case class TestDerived2D[A, B](x: Derived2[A, String], y: Derived2[Int, B]) extends ProductTest derives Eq inspect[TestDerived2A]() - inspect[TestDerived2B[Int, String]]() - inspect[TestDerived2C[Int, String]]( "A".noContextBound ) - inspect[TestDerived2D[Int, String]]( "A".noContextBound, "B".noContextBound ) } - s"Complex" - { + "Complex" - { case class Plain1[A](a: A) case class Plain2[A, B](a: A, b: B) case class Derived2[A: Eq, B: Eq](a: A, b: B) derives Eq + case class Complex[A: Eq, B, C, D, E <: AnyVal, F, G <: B]( + x: Derived2[A, B], + y: Map[Plain1[C], Plain1[D]], + e: E, + seq: Seq[Plain2[Int, F]] + ) extends ProductTest derives Eq - case class Complex[ - A: Eq, - B, - C, - D, - E <: AnyVal, - F, - G <: B]( - x: Derived2[A, B], - y: Map[Plain1[C], Plain1[D]], - e: E, - seq: Seq[Plain2[Int, F]] - ) extends EqTest derives Eq inspect[ Complex[Int, String, Int, String, Boolean, Double | BigDecimal, String] ]( @@ -161,24 +135,17 @@ class DryRunTest extends AnyFreeSpec: ) } - s"Speci4lComplex" - { + "Speci4lComplex" - { case class `Speci4lPlain1`[`*`](a: `*`) case class `Speci4lPlain2`[`+`, `@`](a: `+`, b: `@`) case class `Speci4lDerived2`[`=`: Eq, `^`: Eq](a: `=`, b: `^`) derives Eq - - case class `Speci4lComplex`[ - `#`: Eq, - `&`, - `%`, - `~`, - `-` <: AnyVal, - `/`, - G <: `&`]( - x: `Speci4lDerived2`[`#`, `&`], - y: Map[`Speci4lPlain1`[`%`], `Speci4lPlain1`[`~`]], - e: `-`, - seq: Seq[`Speci4lPlain2`[Int, `/`]] - ) extends EqTest derives Eq + case class `Speci4lComplex`[`#`: Eq, `&`, `%`, `~`, `-` <: AnyVal, `/`, G <: `&`]( + x: `Speci4lDerived2`[`#`, `&`], + y: Map[`Speci4lPlain1`[`%`], `Speci4lPlain1`[`~`]], + e: `-`, + seq: Seq[`Speci4lPlain2`[Int, `/`]] + ) extends ProductTest derives Eq + inspect[ `Speci4lComplex`[Int, String, Int, String, Boolean, Double | BigDecimal, String], ]( @@ -193,17 +160,14 @@ class DryRunTest extends AnyFreeSpec: } } - inline def inspect[T <: EqTest : Eq](expectedViolations: String*): Unit = + private inline def inspect[T <: ProductTest: Eq](expectedViolations: String*): Unit = inspect(nameOf[T], expectedViolations.toSeq) - def inspect[T <: EqTest : Eq](typeName: String, - expectedViolations: Seq[String]): Unit = - - s"Eq[$typeName]" in : + private def inspect[T <: ProductTest: Eq](typeName: String, expectedViolations: Seq[String]): Unit = + s"Eq[$typeName]" in: val violations = summon[Eq[T]].violations - assert(violations === expectedViolations) - + assert(violations == expectedViolations) extension (tpe: String) def notProduct = @@ -215,4 +179,4 @@ class DryRunTest extends AnyFreeSpec: def cannotCompare = s"Values of types $tpe and $tpe cannot be compared with == or !=" -end DryRunTest \ No newline at end of file +end DryRunTest diff --git a/eq/src/test/scala/equality/SummonTest.scala b/equality/src/test/scala/equality/SummonTest.scala similarity index 94% rename from eq/src/test/scala/equality/SummonTest.scala rename to equality/src/test/scala/equality/SummonTest.scala index c8c6d596..8e1b6002 100644 --- a/eq/src/test/scala/equality/SummonTest.scala +++ b/equality/src/test/scala/equality/SummonTest.scala @@ -1,9 +1,9 @@ package equality -import equality.all.{*, given} +import equality.core.{*, given} import org.scalatest.freespec.AnyFreeSpec -case class Box[+A: Eq](a: A)derives Eq +case class Box[+A: Eq](a: A) derives Eq class SummonTest extends AnyFreeSpec: "" - { diff --git a/equality/src/test/scala/equality/TypeNameUtil.scala b/equality/src/test/scala/equality/TypeNameUtil.scala new file mode 100644 index 00000000..801f7634 --- /dev/null +++ b/equality/src/test/scala/equality/TypeNameUtil.scala @@ -0,0 +1,13 @@ +package equality + +import scala.quoted.{Expr, Quotes, Type, quotes} + +object TypeNameUtil: + + inline def nameOf[T]: String = ${ nameOf[T] } + + def nameOf[T: Type](using Quotes): Expr[String] = + import quotes.reflect.{Printer, TypeRepr} + + val typeRepr = TypeRepr.of[T] + Expr(typeRepr.show(using Printer.TypeReprShortCode)) diff --git a/eq/src/test/scala/equality/TypeParameterSupportTest.scala b/equality/src/test/scala/equality/TypeSupportTest.scala similarity index 71% rename from eq/src/test/scala/equality/TypeParameterSupportTest.scala rename to equality/src/test/scala/equality/TypeSupportTest.scala index 2d877712..a3396401 100644 --- a/eq/src/test/scala/equality/TypeParameterSupportTest.scala +++ b/equality/src/test/scala/equality/TypeSupportTest.scala @@ -1,39 +1,39 @@ package equality -import equality.TypeParameterSupport.* +import equality.core.TypeSupport.* import org.scalatest.freespec.AnyFreeSpec -class TypeParameterSupportTest extends AnyFreeSpec: +class TypeSupportTest extends AnyFreeSpec: "" - { "extractTypeParameters" - { "1 parameter" - { "plain" in : assert: - "mypackage.X.Box[other.Z.Bag[bingo.Mill[A], bingo.Mill[A]]]".extractTypeParams == Seq("A") + "mypackage.X.Box[other.Z.Bag[bingo.Mill[A], bingo.Mill[A]]]".extractTypeParameters == Seq("A") "as tuple" in : assert: - "mypackage.X.Box[other.Z.Bag[bingo.Mill[(A,A)], bingo.Mill[(A,A)]]]".extractTypeParams == Seq("A") + "mypackage.X.Box[other.Z.Bag[bingo.Mill[(A,A)], bingo.Mill[(A,A)]]]".extractTypeParameters == Seq("A") } "2 parameters" - { "plain" in : assert: - "mypackage.X.Box[other.Z.Bag[bingo.Mill[A], bingo.Mill[B]]]".extractTypeParams == Seq("A", "B") + "mypackage.X.Box[other.Z.Bag[bingo.Mill[A], bingo.Mill[B]]]".extractTypeParameters == Seq("A", "B") "as tuple" in : assert: - "mypackage.X.Box[other.Z.Bag[bingo.Mill[(A,B)], bingo.Mill[(A,B)]]]".extractTypeParams == Seq("A", "B") + "mypackage.X.Box[other.Z.Bag[bingo.Mill[(A,B)], bingo.Mill[(A,B)]]]".extractTypeParameters == Seq("A", "B") } "3 parameters" - { "plain" in : assert: - "mypackage.X.Box[other.Z.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".extractTypeParams == Seq("A", "B", "C") + "mypackage.X.Box[other.Z.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".extractTypeParameters == Seq("A", "B", "C") "as tuple" in : assert: - "mypackage.X.Box[other.Z.Bag[bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)]]]".extractTypeParams == Seq("A", "B", "C") + "mypackage.X.Box[other.Z.Bag[bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)]]]".extractTypeParameters == Seq("A", "B", "C") } } "replaceTypeParameterWithWildcard" - { @@ -41,53 +41,53 @@ class TypeParameterSupportTest extends AnyFreeSpec: "1 parameter" - { "plain" in : assert: - "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[A]]]".replaceTypeParameterWithWildcard("A") == + "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[A]]]".wildcardTypeParameter("A") == "mypackage.A.Box[other.B.Bag[bingo.Mill[?], bingo.Mill[?]]]" "as tuple" in : assert: - "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,A)], bingo.Mill[(A,A)])]]".replaceTypeParameterWithWildcard("A") == + "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,A)], bingo.Mill[(A,A)])]]".wildcardTypeParameter("A") == "mypackage.A.Box[other.B.Bag[(bingo.Mill[(?,?)], bingo.Mill[(?,?)])]]" } "2 parameters" - { "plain" in : assert: - "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B]]]".replaceTypeParameterWithWildcard("A") == + "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B]]]".wildcardTypeParameter("A") == "mypackage.A.Box[other.B.Bag[bingo.Mill[?], bingo.Mill[B]]]" - "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B]]]".replaceTypeParameterWithWildcard("B") == + "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B]]]".wildcardTypeParameter("B") == "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[?]]]" "as tuple" in : assert: - "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B)], bingo.Mill[(A,B)])]]".replaceTypeParameterWithWildcard("A") == + "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B)], bingo.Mill[(A,B)])]]".wildcardTypeParameter("A") == "mypackage.A.Box[other.B.Bag[(bingo.Mill[(?,B)], bingo.Mill[(?,B)])]]" - "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B)], bingo.Mill[(A,B)])]]".replaceTypeParameterWithWildcard("B") == + "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B)], bingo.Mill[(A,B)])]]".wildcardTypeParameter("B") == "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,?)], bingo.Mill[(A,?)])]]" } "3 parameters" - { "plain" in : assert: - "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".replaceTypeParameterWithWildcard("A") == + "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".wildcardTypeParameter("A") == "mypackage.A.Box[other.B.Bag[bingo.Mill[?], bingo.Mill[B], bingo.Mill[C]]]" - "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".replaceTypeParameterWithWildcard("B") == + "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".wildcardTypeParameter("B") == "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[?], bingo.Mill[C]]]" - "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".replaceTypeParameterWithWildcard("C") == + "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[C]]]".wildcardTypeParameter("C") == "mypackage.A.Box[other.B.Bag[bingo.Mill[A], bingo.Mill[B], bingo.Mill[?]]]" "as tuple" in : assert: - "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)])]]".replaceTypeParameterWithWildcard("A") == + "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)])]]".wildcardTypeParameter("A") == "mypackage.A.Box[other.B.Bag[(bingo.Mill[(?,B,C)], bingo.Mill[(?,B,C)], bingo.Mill[(?,B,C)])]]" - "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)])]]".replaceTypeParameterWithWildcard("B") == + "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)])]]".wildcardTypeParameter("B") == "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,?,C)], bingo.Mill[(A,?,C)], bingo.Mill[(A,?,C)])]]" - "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)])]]".replaceTypeParameterWithWildcard("C") == + "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)], bingo.Mill[(A,B,C)])]]".wildcardTypeParameter("C") == "mypackage.A.Box[other.B.Bag[(bingo.Mill[(A,B,?)], bingo.Mill[(A,B,?)], bingo.Mill[(A,B,?)])]]" } } - } + } \ No newline at end of file diff --git a/examples/src/main/scala/examples/Examples.scala b/examples/src/main/scala/examples/Examples.scala new file mode 100644 index 00000000..056a94f2 --- /dev/null +++ b/examples/src/main/scala/examples/Examples.scala @@ -0,0 +1,243 @@ +package examples + + +@main def examples(): Unit = + quickstart + value_derives_verified_case_class + value_derives_verified_parametrized_case_class + value_given_verified_enum + value_derives_assumed_bottom_class_hierarchy + value_derives_assumed_base_class + value_given_assumed_arbitrary_type + reference_derives_verified_final_class + reference_derives_assumed_final_class + reference_given_verified_final_class + reference_given_assumed_final_class + standard_instances + collection_extensions + universal_equality + number_equality + + +def quickstart: Unit = + import java.time.LocalDateTime + import java.util.jar.Attributes + + // Use equality for a standard library type out of the box + val now = LocalDateTime.now + now == now + + // Explicitly assume equality for an arbitrary type + given Eq[Attributes] = Eq.assumed + Attributes() == Attributes() + + // Derive equality for a product type + case class Box[T: Eq]( + name: String, + item: Either[String, T] + ) derives Eq + val box = Box("", Right(Attributes())) + box == box + + // Use an equality-safe alternative to .contains() + val names = List(now, now) + names.contains_eq(now) + + +def value_derives_verified_case_class: Unit = + case class Email(address: String) derives Eq + + // Compiles because Email derives Eq + case class Person( + name: String, + contact: Email, + ) derives Eq + + val person = Person("Alice", Email("alice@example.net")) + + // Compiles because Person derives Eq + person == person + + +def value_derives_verified_parametrized_case_class: Unit = + case class Email(address: String) derives Eq + + // Compiles because the type parameter T is declared with a context bound [T: Eq] + case class Person[T: Eq]( + name: String, + contact: T, + ) derives Eq + + // Compiles because Email derives Eq + val person = Person("Alice", Email("alice@example.net")) + + // Compiles because Person derives Eq + person == person + +def value_given_verified_enum: Unit = + enum Weekday: + case Monday, Tuesday, Wednesday // ... + + // Compiles because Weekday is a product type with all options supporting equality + given Eq[Weekday] = Eq.derived + + // Compiles because given Eq type class instance for Weekday is in scope + Weekday.Monday == Weekday.Monday + + +def value_derives_assumed_bottom_class_hierarchy: Unit = + class Animal + case class Cat() extends Animal derives Eq.assumed + case class Dog() extends Animal + + val cat = Cat() + val dog = Dog() + + // Compiles because the selected bottom class of this hierarchy derives Eq + cat == cat + + // dog == dog + // ERROR: Values of types Dog and Dog cannot be compared with == or != + + // cat != dog + // ERROR: Values of types Cat and Dog cannot be compared with == or != + + +def value_derives_assumed_base_class: Unit = + class Animal derives Eq.assumed + case class Cat() extends Animal + case class Dog() extends Animal + + val cat = Cat() + val dog = Dog() + + // Compiles because the base class of this hierarchy derives Eq + cat == cat + dog == dog + cat != dog + + +def value_given_assumed_arbitrary_type: Unit = + import java.util.jar.Attributes + + given Eq[Attributes] = Eq.assumed + + val attributes = Attributes() + + // Compiles because given Eq type class instance for Attributes is in scope + attributes == attributes + + +def reference_derives_verified_final_class: Unit = + // Compiles because Item cannot be extended and does not override the equals() method + final class Item(val id: String) derives EqRef + + val item = Item("") + + // Compiles because given EqRef type class instance for Item is in scope + item eqRef item + + +def reference_given_verified_final_class: Unit = + final class Item(id: String) + + // Compiles because Item is cannot be extended and overrides the equals() method + given EqRef[Item] = EqRef.derived + + val item = Item("") + + // Compiles because given EqRef type class instance for Item is in scope + item neRef item + + +def reference_derives_assumed_final_class: Unit = + final class Item(val id: String) derives EqRef.assumed + + val item = Item("") + + // Compiles because Item derives EqRef + item eqRef item + + +def reference_given_assumed_final_class: Unit = + final class Item(val id: String) + + given EqRef[Item] = EqRef.assumed + + val item = Item("") + + // Compiles because given EqRef type class instance for Item is in scope + item neRef item + + +def standard_instances: Unit = + import java.time.{LocalDate, LocalDateTime} + + val now = LocalDateTime.now + val later = LocalDateTime.now + + // Compiles out of the box + now == later + + val today = LocalDate.now + + // today != now + // ERROR: Values of types LocalDate and LocalDateTime cannot be compared with == or != + + +def collection_extensions:Unit = + case class Apple(x: String) derives Eq + val appleA = Apple("A") + val appleB = Apple("B") + val apples = List(appleA, appleB) + + case class Car(x: String) derives Eq + val carY = Car("Y") + val carX = Car("X") + val cars = List(carX, carY) + + // Compiles but it should not since it is meaningless and always returns false + apples.contains(carX) + + // apples.contains_eq(carX) + // ERROR: Values of types A and A cannot be compared with == or != + // where: A is a type variable with constraint >: Apple | Car + + // Compiles but it should not since it is meaningless and always returns the original list + apples.diff(cars) + + // apples.diff_eq(cars) + // ERROR: Values of types Apple and Apple | Car cannot be compared with == or != + + +def hybrid_equality: Unit = + // Import to disable strict equality + import equality.hybrid.given + + class Item(val id: String) derives EqRef.assumed + + val item = Item("") + + // Compiles because hybrid equality is enabled + item == item + + +def universal_equality: Unit = + // Import to disable strict equality + import equality.universal.given + + // Compiles because universal equality is enabled + 1 == true + + +def number_equality: Unit = + // Covariant type parameter T + case class Box[+T: Eq](value: T) derives Eq + + val box1 = Box(Seq(Some( (true, 'a', 1 ) ))) + val box2 = Box(Seq(Some( (true, 'a', 1L ) ))) + + // Compiles because type parameter T is covariant and an instance of + // Eq[ Box[Seq[Option[ (Boolean, Char, AnyNumber) ]]] ] + // is available and can be applied both to Eq[box1.type] and Eq[box2.type]. + box1 == box2 diff --git a/examples/src/main/scala/examples/Quickstart.scala b/examples/src/main/scala/examples/Quickstart.scala deleted file mode 100644 index 351fd99b..00000000 --- a/examples/src/main/scala/examples/Quickstart.scala +++ /dev/null @@ -1,214 +0,0 @@ -package examples - -import scala.annotation.nowarn - -@main def quickStart():Unit = - getting_started - verified_equality_for_composed_case_classes_via_type_class_derivation - verified_equality_for_composed_case_classes_with_type_parameters_via_type_class_derivation - verified_equality_for_an_existing_arbitrary_class_with_a_given - assumed_equality_for_the_bottom_classes_of_a_class_hierarchy_via_type_class_derivation - assumed_equality_for_the_base_class_of_a_class_hierarchy_via_type_class_derivation - assumed_equality_for_an_existing_arbitrary_class_with_a_given - standardEqInstances - collectionExtension - strict_equality_opt_out_for_an_entire_compilation_unit - strict_equality_opt_out_for_a_local_scope - numberEquality - enums - - -def getting_started: Unit = - import java.time.LocalDateTime - import java.util.jar.Attributes - - // Use equality for a standard library type out of the box - val now = LocalDateTime.now - now == now - - // Explicitly assume equality for an arbitrary type - given Eq[Attributes] = Eq.assumed - Attributes() == Attributes() - - // Derive equality for a product type - case class Box[A: Eq]( - name: String, - item: Either[String, A] - ) derives Eq - val box = Box("my box", Right(Attributes())) - box == box - - // Use an equality-safe alternative to .contains() - val names = List(now, now) - names.contains_eq(now) - - -@nowarn -def verified_equality_for_composed_case_classes_via_type_class_derivation: Unit = - - case class Email( address:String) derives Eq - - // Only compiles because class Email derives Eq - case class Person( - name: String, - contact: Email, - ) derives Eq - - val person = Person("Alice", Email("alice@maluma.osw")) - - // Only compiles because class Person derives Eq - person == person - - -@nowarn -def verified_equality_for_composed_case_classes_with_type_parameters_via_type_class_derivation: Unit = - - case class Email( address:String) derives Eq - - // Only compiles because the type parameter A is declared with a context bound [A: Eq] - case class Person[A: Eq]( - name: String, - contact: A, - ) derives Eq - - // Only compiles because class Email derives Eq - val person = Person("Alice", Email("alice@maluma.osw")) - - // Only compiles because class Person derives Eq - person == person - -case class SomeProduct() -@nowarn -def verified_equality_for_an_existing_arbitrary_class_with_a_given: Unit = - - // Only compiles because SomeProduct conforms to the equality rules - given Eq[SomeProduct] = Eq.derived - - // Only compiles with the given instance above - SomeProduct() == SomeProduct() - - -@nowarn -def assumed_equality_for_the_bottom_classes_of_a_class_hierarchy_via_type_class_derivation: Unit = - - class Animal - case class Cat() extends Animal derives Eq.assumed - case class Dog() extends Animal derives Eq.assumed - - // Values of type Cat can compare each other with == or != - // Values of type Dog can compare each other with == or != - // Within this hierarchy, any other comparison with == or != fails - - -@nowarn -def assumed_equality_for_the_base_class_of_a_class_hierarchy_via_type_class_derivation: Unit = - - class Animal derives Eq.assumed - case class Cat() extends Animal - case class Dog() extends Animal - - // Within this hierarchy, any value can compare to any other value with == or != - - -@nowarn -def assumed_equality_for_an_existing_arbitrary_class_with_a_given: Unit = - - import java.util.jar.Attributes - - // Always compiles - given Eq[Attributes] = Eq.assumed - - // Only compiles with the given instance above - Attributes() == Attributes() - - -@nowarn -def standardEqInstances: Unit = - - import java.time.{LocalDate, LocalDateTime} - - val now = LocalDateTime.now - val later = LocalDateTime.now - - // Compiles out of the box - now == later - - val today = LocalDate.now - - // today == now - // ERROR: Values of types LocalDate and LocalDateTime cannot be compared with == or != - - -@nowarn -def collectionExtension:Unit = - - case class Apple(x: String) derives Eq - val appleA = Apple("A") - val appleB = Apple("B") - val appleC = Apple("C") - val apples = List(appleA, appleB, appleC) - - case class Car(x: String) derives Eq - val carY = Car("Y") - val carX = Car("X") - val cars = List(carX, carY) - - // It's pointless to search for a car in a list of apples - apples.contains(carX) - // Type checks but it shouldn't --> yields false - - // apples.contains_eq(carX) - // ERROR: Values of types A and A cannot be compared with == or != - // where: A is a type variable with constraint >: Apple | Car - - // It is pointless to remove a list of cars from a list of apples - apples.diff(cars) - // Type checks but it shouldn't --> returns the original list - - // apples.diff_eq(cars) - // ERROR: Values of types Apple and Apple | Car cannot be compared with == or != - - -@nowarn -def strict_equality_opt_out_for_an_entire_compilation_unit: Unit = - // Import to disable strict equality - import equality.universal.given - - // Only compiles because strict equality is disabled - val _ = 1 == "abc" - - -@nowarn -def strict_equality_opt_out_for_a_local_scope: Unit = - def areEqual(x1: Any, x2: Any): Boolean = - // Import to locally disable strict equality - import equality.universal.given - - // Only compiles because strict equality is disabled - x1 == x2 - - -def numberEquality: Unit = - - // Covariant type parameter A - case class Box[+A: Eq](a: A) derives Eq - - val box1 = Box(Seq(Some( (true, 'a', 1 ) ))) - val box2 = Box(Seq(Some( (true, 'a', 1L ) ))) - - // Compiles because type parameter A is covariant and an instance of - // Eq[ Box[Seq[Option[ (Boolean, Char, AnyNumber) ]]] ] - // is available and can be applied both to Eq[box1.type] and Eq[box2.type]. - box1 == box2 - - -@nowarn -def enums:Unit = - - enum Weekday derives Eq: - // These are instances of the product type Weekday - case Monday, Tuesday, Wednesday // ... - - // Only compiles because enum Weekday derives Eq - val myDay: Weekday = Weekday.Monday - myDay == myDay diff --git a/project/build.properties b/project/build.properties index 40b3b8e7..875b706a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.0 +sbt.version=1.9.2 diff --git a/site/StandardEqInstances.md b/site/StandardEqInstances.md deleted file mode 100644 index 05e7741e..00000000 --- a/site/StandardEqInstances.md +++ /dev/null @@ -1,118 +0,0 @@ -**[Type safe equality for Scala 3](https://github.com/antognini/type-safe-equality)** - -## List of defined equality type class instances - -Equality works out of the box for the types and listed in this table, including all their subtypes. -Equality rules are inferred correctly on those types when using `Unit`, `Null` and `Nothing` as type parameters. - -
- - -| package | Eq instance | equality for type | -|--------------------------------------|------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| `equality.scala_` | `scala_Array` | [scala.Array](https://scala-lang.org/api/3.x/scala/Array.html) | -| | `scala_Boolean` | [scala.Boolean](https://scala-lang.org/api/3.x/scala/Boolean.html) | -| | `scala_Byte` | [scala.Byte](https://scala-lang.org/api/3.x/scala/Byte.html) | -| | `scala_Char` | [scala.Char](https://scala-lang.org/api/3.x/scala/Char.html) | -| | `scala_Double` | [scala.Double](https://scala-lang.org/api/3.x/scala/Double.html) | -| | `scala_Float` | [scala.Float](https://scala-lang.org/api/3.x/scala/Float.html) | -| | `scala_Int` | [scala.Int](https://scala-lang.org/api/3.x/scala/Int.html) | -| | `scala_Long` | [scala.Long](https://scala-lang.org/api/3.x/scala/Long.html) | -| | `scala_Nothing` | [scala.Nothing](https://scala-lang.org/api/3.x/scala/Nothing.html) | -| | `scala_Null` | [scala.Null](https://scala-lang.org/api/3.x/scala/Null.html) | -| | `scala_Option` | [scala.Option](https://scala-lang.org/api/3.x/scala/Option.html) | -| | `scala_Short` | [scala.Short](https://scala-lang.org/api/3.x/scala/Short.html) | -| | `scala_String` | [scala.String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html) | -| | `scala_Tuple0`
`scala_Tuple1`
...
`scala_Tuple31` | [scala.EmptyTuple](https://scala-lang.org/api/3.x/scala/EmptyTuple$.html)
`T1 *: EmptyTuple`
...
`T1 *: T2 *: ... *: T31 *: EmptyTuple` | -| | `scala_Unit` | [scala.Unit](https://scala-lang.org/api/3.x/scala/Unit.html) | -| `equality.scala_collection` | `scala_collection_Map` | [scala.collection.Map](https://scala-lang.org/api/3.x/scala/collection/Map.html) | -| | `scala_collection_Set` | [scala.collection.Set](https://scala-lang.org/api/3.x/scala/collection/Set.html) | -| | `scala_collection_Seq` | [scala.collection.Seq](https://scala-lang.org/api/3.x/scala/collection/Seq.html) | -| `equality.scala_concurrent` | `scala_concurrent_Future` | [scala.concurrent.Future](https://scala-lang.org/api/3.x/scala/concurrent/Future.html) | -| | `scala_concurrent_Promise` | [scala.concurrent.Promise](https://scala-lang.org/api/3.x/scala/concurrent/Promise.html) | -| `equality.scala_concurrent_duration` | `scala_concurrent_duration_Deadline` | [scala.concurrent.duration.Deadline](https://scala-lang.org/api/3.x/scala/concurrent/duration/Deadline.html) | -| | `scala_concurrent_duration_Duration` | [scala.concurrent.duration.Duration](https://scala-lang.org/api/3.x/scala/concurrent/duration/Duration.html) | -| `equality.scala_io` | `scala_io_Codec` | [scala.io_Codec](https://scala-lang.org/api/3.x/scala/io/Codec.html) | -| `equality.scala_math` | `scala_math_BigDecimal` | [scala.math.BigDecimal](https://scala-lang.org/api/3.x/scala/math/BigDecimal.html) | -| | `scala_math_BigInt` | [scala.math.BigInt](https://scala-lang.org/api/3.x/scala/math/BigInt.html) | -| `equality.scala_util` | `scala_util_Either` | [scala.util.Either](https://scala-lang.org/api/3.x/scala/util/Either.html) | -| | `scala_util_Try` | [scala.util.Try](https://scala-lang.org/api/3.x/scala/util/Try.html) | -| `equality.java_io` | `java_io_FileDescriptor` | [java.io.FileDescriptor](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/FileDescriptor.html) | -| | `java_io_File` | [java.io.File](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/File.html) | -| | `java_io_InputStream` | [java.io.InputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html) | -| | `java_io_OutputStream` | [java.io.OutputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/OutputStream.html) | -| | `java_io_Reader` | [java.io.Reader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/Reader.html) | -| | `java_io_Writer` | [java.io.Writer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/Writer.html) | -| `equality.java_io_charset` | `java_nio_charset_Charset` | [java.nio.charset.Charset](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html) | -| `equality.java_lang` | `java_lang_Boolean` | [java.lang.Boolean](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Boolean.html) | -| | `java_lang_Byte` | [java.lang.Byte](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Byte.html) | -| | `java_lang_Character` | [java.lang.Character](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html) | -| | `java_lang_Double` | [java.lang.Double](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Double.html) | -| | `java_lang_Enum` | [java.lang.Enum](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html) | -| | `java_lang_Float` | [java.lang.Float](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Float.html) | -| | `java_lang_Integer` | [java.lang.Integer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Integer.html) | -| | `java_lang_Short` | [java.lang.Short](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Short.html) | -| | `java_lang_Thread` | [java.lang.Thread](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html) | -| | `java_lang_Throwable` | [java.lang.Throwable](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Throwable.html) | -| | `java_lang_Void` | [java.lang.Void](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Void.html) | -| | `java_lang_Number` | [java.lang.Number](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Number.html) | -| `equality.java_math` | `java_math_BigInteger` | [java.math.BigInteger](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigInteger.html) | -| | `java_math_MathContext` | [java.math.MathContext](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/MathContext.html) | -| | `java_math_RoundingMode` | [java.math.RoundingMode](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/RoundingMode.html) | -| `equality.java_net` | `java_net_HttpCookie` | [java.net.HttpCookie](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html) | -| | `java_net_IDN` | [java.net.IDN](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/IDN.html) | -| | `java_net_Inet4Address` | [java.net.Inet4Address](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Inet4Address.html) | -| | `java_net_Inet6Address` | [java.net.Inet6Address](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Inet6Address.html) | -| | `java_net_MulticastSocket` | [java.net.MulticastSocket](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/MulticastSocket.html) | -| | `java_net_NetworkInterface` | [java.net.NetworkInterface](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/NetworkInterface.html) | -| | `java_net_ProtocolFamily` | [java.net.ProtocolFamily](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/ProtocolFamily.html) | -| | `java_net_ServerSocket` | [java.net.ServerSocket](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/ServerSocket.html) | -| | `java_net_SocketOption` | [java.net.SocketOption](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/SocketOption.html) | -| | `java_net_SocketOptions` | [java.net.SocketOptions](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/SocketOptions.html) | -| | `java_net_Socket` | [java.net.Socket](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Socket.html) | -| | `java_net_URI` | [java.net.URI](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URI.html) | -| | `java_net_URL` | [java.net.URL](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URL.html) | -| `equality.java_nio` | `java_nio_ByteBuffer` | [java.nio.ByteBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ByteBuffer.html) | -| | `java_nio_ByteOrder` | [java.nio.ByteOrder](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ByteOrder.html) | -| | `java_nio_DoubleBuffer` | [java.nio.DoubleBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/DoubleBuffer.html) | -| | `java_nio_FloatBuffer` | [java.nio.FloatBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/FloatBuffer.html) | -| | `java_nio_IntBuffer` | [java.nio.IntBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/IntBuffer.html) | -| | `java_nio_LongBuffer` | [java.nio.LongBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/LongBuffer.html) | -| | `java_nio_ShortBuffer` | [java.nio.ShortBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ShortBuffer.html) | -| `equality.java_nio_charset` | `java_nio_charset_Charset` | [java.nio.charset.Charset](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html) | -| `equality.java_nio_file` | `java_nio_file_Path` | [java.nio.file.Path](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html) | -| `equality.java_security` | `java_security_Permission` | [java.security.Permission](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/Permission.html) | -| | `java_security_Principal` | [java.security.Principal](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/Principal.html) | -| | `java_security_Timestamp` | [java.security.Timestamp](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/Timestamp.html) | -| `equality.java_sql` | `java_sql_SQLType ` | [java.sql.SQLType](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLType.html) | -| `equality.java_text` | `java_text_Collator` | [java.text.Collator](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/Collator.html) | -| | `java_text_Format` | [java.text.Format](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/Format.html) | -| `equality.java_time` | `java_time_DayOfWeek` | [java.time.DayOfWeek](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/DayOfWeek.html) | -| | `java_time_Duration` | [java.time.Duration](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Duration.html) | -| | `java_time_Instant` | [java.time.Instant](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Instant.html) | -| | `java_time_LocalDateTime` | [java.time.LocalDateTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalDateTime.html) | -| | `java_time_LocalDate` | [java.time.LocalDate](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalDate.html) | -| | `java_time_LocalTime` | [java.time.LocalTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalTime.html) | -| | `java_time_MonthDay` | [java.time.MonthDay](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/MonthDay.html) | -| | `java_time_Month` | [java.time.Month](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Month.html) | -| | `java_time_OffsetDateTime` | [java.time.OffsetDateTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/OffsetDateTime.html) | -| | `java_time_OffsetTime` | [java.time.OffsetTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/OffsetTime.html) | -| | `java_time_Period` | [java.time.Period](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Period.html) | -| | `java_time_YearMonth` | [java.time.YearMonth](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/YearMonth.html) | -| | `java_time_Year` | [java.time.Year](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Year.html) | -| | `java_time_ZoneId` | [java.time.ZoneId](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html) | -| | `java_time_ZoneOffset` | [java.time.ZoneOffset](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneOffset.html) | -| | `java_time_ZonedDateTime` | [java.time.ZonedDateTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZonedDateTime.html) | -| `equality.java_util` | `java_util_Date` | [java.util.Date](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Date.html) | -| | `java_util_List` | [java.util.List](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) | -| | `java_util_Locale_LanguageRange` | [java.util.Locale.LanguageRange](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Locale.LanguageRange.html) | -| | `java_util_Locale` | [java.util.Locale](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Locale.html) | -| | `java_util_Map` | [java.util.Map](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Map.html) | -| | `java_util_OptionalDouble` | [java.util.OptionalDouble](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/OptionalDouble.html) | -| | `java_util_OptionalInt` | [java.util.OptionalInt](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/OptionalInt.html) | -| | `java_util_OptionalLong` | [java.util.OptionalLong](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/OptionalLong.html) | -| | `java_util_Optional` | [java.util.Optional](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html) | -| | `java_util_Properties` | [java.util.Properties](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Properties.html) | -| | `java_util_Queue` | [java.util.Queue](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Queue.html) | -| | `java_util_Set` | [java.util.Set](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Set.html) | -| | `java_util_TimeZone` | [java.util.TimeZone](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TimeZone.html) | diff --git a/site/example-ide-1h.png b/site/example-ide-1h.png deleted file mode 100644 index 78e081ef..00000000 Binary files a/site/example-ide-1h.png and /dev/null differ