Skip to content

Commit a76f3cc

Browse files
committed
Change rules for multiversal equality
1. Don't apply the lifting rules when strictEquality is set. 2. Be more strict about null checking 3. Compensate for (2) by changing errorTerm in parser so that it has an error type instead of Null. 4. Reorganize implementation to match new docs. 5. Handle Eql instances involving primitive types or Null directly in the compiler (previously, we were missing some of them, see pos/multivarsal.scala as a test) 6. Sharpen Eql instance of Proxy to only work for AnyRef arguments. This avoids false negatives when comparing value types with null.
1 parent 9df6423 commit a76f3cc

File tree

11 files changed

+161
-113
lines changed

11 files changed

+161
-113
lines changed

compiler/src/dotty/tools/dotc/core/Mode.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ object Mode {
5252
/** Allow GADTFlexType labelled types to have their bounds adjusted */
5353
val GADTflexible: Mode = newMode(8, "GADTflexible")
5454

55+
/** Assume -language:strictEquality */
56+
val StrictEquality: Mode = newMode(9, "StrictEquality")
57+
5558
/** We are currently printing something: avoid to produce more logs about
5659
* the printing
5760
*/

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,9 @@ object Parsers {
324324
accept(SEMI)
325325
}
326326

327-
def errorTermTree: Literal = atSpan(in.offset) { Literal(Constant(null)) }
327+
def errorTermTree: Tree = atSpan(in.offset) {
328+
TypedSplice(Literal(Constant(null)).withType(Types.UnspecifiedErrorType))
329+
}
328330

329331
private[this] var inFunReturnType = false
330332
private def fromWithinReturnType[T](body: => T): T = {

docs/docs/reference/other-new-features/multiversal-equality.md

Lines changed: 101 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,112 +9,150 @@ the fact that `==` and `!=` are implemented in terms of Java's
99
`equals` method, which can also compare values of any two reference
1010
types.
1111

12-
Universal equality is convenient but also dangerous since it
13-
undermines type safety. Say you have an erroneous program where
14-
a value `y` has type `S` instead of the expected type `T`.
12+
Universal equality is convenient. But it is also dangerous since it
13+
undermines type safety. For instance, let's assume one is left after some refactoring
14+
with an erroneous program where a value `y` has type `S` instead of the correct type `T`.
1515

1616
```scala
1717
val x = ... // of type T
1818
val y = ... // of type S, but should be T
1919
x == y // typechecks, will always yield false
2020
```
2121

22-
If all you do with `y` is compare it to other values of type `T`, the program will
23-
typecheck but probably give unexpected results.
22+
If all the program does with `y` is compare it to other values of type `T`, the program will still typecheck, since values of all types can be compared with each other.
23+
But it will probably give unexpected results and fail at runtime.
2424

2525
Multiversal equality is an opt-in way to make universal equality
26-
safer. The idea is that by declaring an `implicit` value one can
27-
restrict the types that are legal in comparisons. The example above
28-
would not typecheck if an implicit was declared like this for type `T`
29-
(or an analogous one for type `S`):
30-
26+
safer. It uses a binary typeclass `Eql` to indicate that values of
27+
two given types can be compared with each other.
28+
The example above would not typecheck if `S` or `T` was a class
29+
that derives `Eql`, e.g.
3130
```scala
32-
implicit def eqT: Eq[T, T] = Eq.derived
31+
class T derives Eql
3332
```
34-
35-
This definition effectively says that value of type `T` can (only) be
36-
compared with `==` or `!=` to other values of type `T`. The definition
37-
is used only for type checking; it has no significance for runtime
33+
Alternatively, one can also provide the derived implied instance directly, like this:
34+
```scala
35+
implied for Eql[T, T] = Eql.derived
36+
```
37+
This definition effectively says that values of type `T` can (only) be
38+
compared to other values of type `T` when using `==` or `!=`. The definition
39+
affects type checking but it has no significance for runtime
3840
behavior, since `==` always maps to `equals` and `!=` always maps to
39-
the negation of `equals`. The right hand side of the definition is a value
40-
that has any `Eq` instance as its type. Here is the definition of class
41-
`Eq` and its companion object:
42-
41+
the negation of `equals`. The right hand side `Eql.derived` of the definition
42+
is a value that has any `Eql` instance as its type. Here is the definition of class
43+
`Eql` and its companion object:
4344
```scala
4445
package scala
4546
import annotation.implicitNotFound
4647

4748
@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=")
48-
sealed trait Eq[-L, -R]
49+
sealed trait Eql[-L, -R]
4950

50-
object Eq extends Eq[Any, Any]
51+
object Eql {
52+
object derived extends Eql[Any, Any]
53+
}
5154
```
5255

53-
One can have several `Eq` instances for a type. For example, the four
56+
One can have several `Eql` instances for a type. For example, the four
5457
definitions below make values of type `A` and type `B` comparable with
5558
each other, but not comparable to anything else:
5659

5760
```scala
58-
implicit def eqA : Eq[A, A] = Eq.derived
59-
implicit def eqB : Eq[B, B] = Eq.derived
60-
implicit def eqAB: Eq[A, B] = Eq.derived
61-
implicit def eqBA: Eq[B, A] = Eq.derived
61+
implied for Eql[A, A] = Eql.derived
62+
implied for Eql[B, B] = Eql.derived
63+
implied for Eql[A, B] = Eql.derived
64+
implied for Eql[B, A] = Eql.derived
6265
```
66+
The `scala.Eql` object defines a number of `Eql` instances that together
67+
define a rule book for what standard types can be compared (more details below).
6368

64-
(As usual, the names of the implicit definitions don't matter, we have
65-
chosen `eqA`, ..., `eqBA` only for illustration).
66-
67-
The `scala.Eq` object defines a number of `Eq` implicits that make
68-
values of types `String`, `Boolean` and `Unit` only comparable to
69-
values of the same type. They also make numbers only comparable to
70-
other numbers, sequences only comparable to other
71-
sequences and sets only comparable to other sets.
72-
73-
There's also a "fallback" instance named `eqAny` that allows comparisons
74-
over all types that do not themselves have an `Eq` instance. `eqAny` is
69+
There's also a "fallback" instance named `eqlAny` that allows comparisons
70+
over all types that do not themselves have an `Eql` instance. `eqlAny` is
7571
defined as follows:
7672

7773
```scala
78-
def eqAny[L, R]: Eq[L, R] = Eq.derived
74+
def eqlAny[L, R]: Eql[L, R] = Eql.derived
7975
```
8076

81-
Even though `eqAny` is not declared implicit, the compiler will still
82-
construct an `eqAny` instance as answer to an implicit search for the
83-
type `Eq[L, R]`, provided that neither `L` nor `R` have `Eq` instances
84-
defined on them.
77+
Even though `eqlAny` is not declared `implied`, the compiler will still
78+
construct an `eqlAny` instance as answer to an implicit search for the
79+
type `Eql[L, R]`, unless `L` or `R` have `Eql` instances
80+
defined on them, or the language feature `strictEquality` is enabled
8581

86-
The primary motivation for having `eqAny` is backwards compatibility,
87-
if this is of no concern one can disable `eqAny` by enabling the language
82+
The primary motivation for having `eqlAny` is backwards compatibility,
83+
if this is of no concern one can disable `eqlAny` by enabling the language
8884
feature `strictEquality`. As for all language features this can be either
8985
done with an import
9086

9187
```scala
9288
import scala.language.strictEquality
9389
```
94-
9590
or with a command line option `-language:strictEquality`.
9691

97-
All `enum` types also come with `Eq` instances that make values of the
98-
`enum` type comparable only to other values of that `enum` type.
92+
## Deriving Eql Instances
93+
94+
Instead of defining `Eql` instances directly, it is often more convenient to derive them. Example:
95+
```scala
96+
class Box[T](x: T) derives Eql
97+
```
98+
By the usual rules if [typeclass derivation](./derivation.html),
99+
this generates the following `Eql` instance in the companion object of `Box`:
100+
```scala
101+
implied [T, U] given Eql[T, U] for Eql[Box[T], Box[U]] = Eql.derived
102+
```
103+
That is, two boxes are comparable with `==` or `!=` if their elements are. Examples:
104+
```scala
105+
new Box(1) == new Box(1L) // ok since `Eql[Int, Long]` is an implied instance
106+
new Box(1) == new Box("a") // error: can't compare
107+
new Box(1) == 1 // error: can't compare
108+
```
109+
110+
## Precise Rules for Equality Checking
99111

100112
The precise rules for equality checking are as follows.
101113

102-
1. A comparison using `x == y` or `x != y` between values `x: T` and `y: U`
103-
is legal if either `T` and `U` are the same, or one of the types is a subtype
104-
of the "lifted" version of the other type, or an implicit value of type `scala.Eq[T, U]` is found.
105-
See the [description on Github](https://github.com/lampepfl/dotty/issues/1247) for
106-
a definition of lifting.
107-
108-
2. The usual rules for implicit search apply also to `Eq` instances,
109-
with one modification: If the `strictEquality` feature is not enabled,
110-
an instance of `scala.Eq.eqAny[T, U]` is constructed if neither `T`
111-
nor `U` have a reflexive `Eq` instance themselves. Here, a type `T`
112-
has a reflexive `Eq` instance if the implicit search for `Eq[T, T]`
113-
succeeds and constructs an instance different from `eqAny`.
114-
115-
Here _lifting_ a type `S` means replacing all references to abstract types
116-
in covariant positions of `S` by their upper bound, and to replacing
117-
all refinement types in covariant positions of `S` by their parent.
114+
If the `strictEquality` feature is enabled then
115+
a comparison using `x == y` or `x != y` between values `x: T` and `y: U`
116+
is legal if
117+
118+
1. there is an implied instance of type `Eql[T, U]`, or
119+
2. one of `T`, `U` is `Null`.
120+
121+
In the default case where the `strictEquality` feature is not enabled the comparison is
122+
also legal if
123+
124+
1. `T` and `U` the same, or
125+
2. one of `T` and `U`is a subtype of the _lifted_ version of the other type, or
126+
3. neither `T` nor `U` have a _reflexive `Eql` instance_.
127+
128+
Explanations:
129+
130+
- _lifting_ a type `S` means replacing all references to abstract types
131+
in covariant positions of `S` by their upper bound, and to replacing
132+
all refinement types in covariant positions of `S` by their parent.
133+
- a type `T` has a _reflexive `Eql` instance_ if the implicit search for `Eql[T, T]`
134+
succeeds.
135+
136+
## Predefined Eql Instances
137+
138+
The `Eql` object defines implied instances for
139+
- the primitive types `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, `Double`, `Boolean`, and `Unit`,
140+
- `java.lang.Number`, `java.lang.Boolean`, and `java.lang.Character`,
141+
- `scala.collection.Seq`, and `scala.collection.Set`.
142+
143+
Implied instances are defined so that everyone of these types is has a reflexive `Eql` instance, and the following holds:
144+
145+
- Primitive numeric types can be compared with each other.
146+
- Primitive numeric types can be compared with subtypes of `java.lang.Number` (and _vice versa_).
147+
- `Boolean` can be compared with `java.lang.Boolean` (and _vice versa_).
148+
- `Char` can be compared with `java.lang.Character` (and _vice versa_).
149+
- Two sequences (of arbitrary subtypes of `scala.collection.Seq`) can be compared
150+
with each other if their element types can be compared. The two sequence types
151+
need not be the same.
152+
- Two sets (of arbitrary subtypes of `scala.collection.Set`) can be compared
153+
with each other if their element types can be compared. The two set types
154+
need not be the same.
155+
- Any subtype of `AnyRef` can be compared with `Null` (and _vice versa_).
118156

119157
More on multiversal equality is found in a [blog post](http://www.scala-lang.org/blog/2016/05/06/multiversal-equality.html)
120158
and a [Github issue](https://github.com/lampepfl/dotty/issues/1247).

docs/sidebar.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ sidebar:
117117
- title: Procedure Syntax
118118
url: docs/reference/dropped-features/procedure-syntax.html
119119
- title: Package Objects
120-
uril: docs/reference/dropped-features/package-objects.html
120+
url: docs/reference/dropped-features/package-objects.html
121121
- title: Early Initializers
122122
url: docs/reference/dropped-features/early-initializers.html
123123
- title: Class Shadowing

library/src/scala/Eq.scala

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,31 @@ import scala.collection.{GenSeq, Set}
77
@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=")
88
sealed trait Eq[-L, -R]
99

10-
/** Besides being a companion object, this object
11-
* can also be used as a value that's compatible with
12-
* any instance of `Eq`.
10+
/** Companion object containing a few universally known `Eql` instances.
11+
* Eql instances involving primitive types or the Null type are handled directly in
12+
* the compiler (see Implicits.synthesizedEq), so they are not included here.
1313
*/
1414
object Eq {
15+
/** A non-implied universal `Eql` instance. */
1516
object derived extends Eq[Any, Any]
1617

17-
/** A fall-back "implicit" to compare values of any types.
18-
* Even though this method is not declared implicit, the compiler will
19-
* compute instances as solutions to `Eq[T, U]` queries if `T <: U` or `U <: T`
20-
* or both `T` and `U` are Eq-free. A type `S` is Eq-free if there is no
21-
* implicit instance of type `Eq[S, S]`.
18+
/** A fall-back instance to compare values of any types.
19+
* Even though this method is not declared implied, the compiler will
20+
* compute implied instances as solutions to `Eql[T, U]` queries if
21+
* the rules of multiversal equality require it.
2222
*/
2323
def eqAny[L, R]: Eq[L, R] = derived
2424

25-
// Instances of `Eq` for common types
26-
25+
// Instances of `Eq` for common Java types
2726
implicit def eqNumber : Eq[Number, Number] = derived
2827
implicit def eqString : Eq[String, String] = derived
29-
implicit def eqBoolean : Eq[Boolean, Boolean] = derived
30-
implicit def eqByte : Eq[Byte, Byte] = derived
31-
implicit def eqShort : Eq[Short, Short] = derived
32-
implicit def eqChar : Eq[Char, Char] = derived
33-
implicit def eqInt : Eq[Int, Int] = derived
34-
implicit def eqLong : Eq[Long, Long] = derived
35-
implicit def eqFloat : Eq[Float, Float] = derived
36-
implicit def eqDouble : Eq[Double, Double] = derived
37-
implicit def eqUnit : Eq[Unit, Unit] = derived
38-
39-
// true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies
40-
implicit def eqProxy : Eq[Proxy, Any] = derived
4128

29+
// The next three definitions can go into the companion objects of classes
30+
// Seq, Set, and Proxy. For now they are here in order not to have to touch the
31+
// source code of these classes
4232
implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[GenSeq[T], GenSeq[U]] = derived
4333
implicit def eqSet[T, U](implicit eq: Eq[T, U]): Eq[Set[T], Set[U]] = derived
4434

45-
implicit def eqByteNum : Eq[Byte, Number] = derived
46-
implicit def eqNumByte : Eq[Number, Byte] = derived
47-
implicit def eqCharNum : Eq[Char, Number] = derived
48-
implicit def eqNumChar : Eq[Number, Char] = derived
49-
implicit def eqShortNum : Eq[Short, Number] = derived
50-
implicit def eqNumShort : Eq[Number, Short] = derived
51-
implicit def eqIntNum : Eq[Int, Number] = derived
52-
implicit def eqNumInt : Eq[Number, Int] = derived
53-
implicit def eqLongNum : Eq[Long, Number] = derived
54-
implicit def eqNumLong : Eq[Number, Long] = derived
55-
implicit def eqFloatNum : Eq[Float, Number] = derived
56-
implicit def eqNumFloat : Eq[Number, Float] = derived
57-
implicit def eqDoubleNum: Eq[Double, Number] = derived
58-
implicit def eqNumDouble: Eq[Number, Double] = derived
59-
60-
implicit def eqSBoolJBool: Eq[Boolean, java.lang.Boolean] = derived
61-
implicit def eqJBoolSBool: Eq[java.lang.Boolean, Boolean] = derived
35+
// true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies
36+
implicit def eqProxy : Eq[Proxy, AnyRef] = derived
6237
}

tests/neg/equality.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ object equality {
5858

5959
1 == true // error
6060

61-
null == true // OK by eqProxy or eqJBoolSBool
62-
true == null // OK by eqSBoolJBool
63-
null == 1 // OK by eqProxy or eqNumInt
64-
1 == null // OK by eqIntNum
61+
null == true // error
62+
true == null // error
63+
null == 1 // error
64+
1 == null // error
6565

6666

6767
class Fruit derives Eq

tests/neg/i1707.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ object DepBug {
1616
import d._
1717
a m (b)
1818
}
19-
{ // error: Null does not take parameters (follow on)
19+
{
2020
import dep._
2121
a m (b) // error: not found: a
2222
}
23-
dep.a m (dep b) // error (follow on)
23+
dep.a m (dep b) // error (a is not a member of Object)
2424
}

tests/neg/parser-stability-19.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object x0 {
22
case class x0[] // error // error
3-
def x0( ) ] // error // error
3+
def x0( ) ] // error
44
def x0 ( x0:x0 ):x0.type = x1 x0 // error // error
55
// error

tests/neg/parser-stability-25.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ class D extends (Int => 1) {
1111
}
1212

1313
class Wrap(x: Int)
14-
class E extends (Wrap)( // error
14+
class E extends (Wrap)(
1515
// error

tests/pos/multiversal.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
object Test {
2+
import scala.Eq
3+
4+
implied [X, Y] given Eq[X, Y] for Eq[List[X], List[Y]] = Eq.derived
5+
6+
val b: Byte = 1
7+
val c: Char = 2
8+
val i: Int = 3
9+
val l: Long = 4L
10+
val ii: Integer = i
11+
12+
List(b) == List(l)
13+
List(l) == List(c)
14+
List(b) != List(c)
15+
List(i) == List(l)
16+
List(i) == List(ii)
17+
List(ii) == List(l)
18+
List(b) == List(ii)
19+
List(ii) == List(l)
20+
21+
import reflect.ClassTag
22+
val BooleanTag: ClassTag[Boolean] = ClassTag.Boolean
23+
24+
class Setting[T: ClassTag] {
25+
def doSet() = implicitly[ClassTag[T]] match {
26+
case BooleanTag =>
27+
case _ =>
28+
}
29+
}
30+
}

tests/run/lst/Lst.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ object Lst {
675675
def fromIterable[T](xs: Iterable[T]): Lst[T] = fromIterator(xs.iterator)
676676

677677
object :: {
678-
def unapply[T](xs: Lst[T]): Option[(T, Lst[T])] = xs match {
678+
def unapply[T](xs: Lst[T]): Option[(T, Lst[T])] = xs.elems match {
679679
case null => None
680680
case elems: Arr =>
681681
Some((elems(0).asInstanceOf[T], _fromArray[T](elems, 1, elems.length)))

0 commit comments

Comments
 (0)