/
TypeCheckedTripleEquals.scala
333 lines (323 loc) · 16.3 KB
/
TypeCheckedTripleEquals.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*
* Copyright 2001-2013 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalactic
import TripleEqualsSupport._
/**
* Provides <code>===</code> and <code>!==</code> operators that return <code>Boolean</code>, delegate the equality determination
* to an <code>Equality</code> type class, and require the types of the two values compared to be in a subtype/supertype
* relationship.
*
* <table><tr><td class="usage">
* <strong>Recommended Usage</strong>:
* Trait <code>TypeCheckedTripleEquals</code> is useful (in both production and test code) when you need a stricter type check
* than is provided by the <a href="http://www.scalactic.org/supersafe">SuperSafe Community Edition</a> compiler plugin for
* <a href="TripleEquals.html"><code>TripleEquals</code></a>. For example, if you are developing a library that uses advanced
* features of Scala's type system, you may want to enforce in your tests that the types appearing
* in equality comparisons match exactly.
* </td></tr></table>
*
* <p>
* By default under <code>TripleEquals</code>, any use of <code>===</code> will compile, just like the <code>==</code> operator:
* </p>
*
* <pre class="stREPL">
* scala> import org.scalactic._
* import org.scalactic._
*
* scala> import TripleEquals._
* import TripleEquals._
*
* scala> 1L === 1 // A Long can equal an Int
* res0: Boolean = true
*
* scala> List(1, 2, 3) === Vector(1, 2, 3) // A List can equal a Vector
* res1: Boolean = true
*
* scala> "hi" === 1 // Likely a bug, because a String can never equal an Int
* res2: Boolean = false
* </pre>
*
* <p>
* With <a href="http://www.scalactic.org/supersafe">SuperSafe Community Edition</a> installed, the first two expressions
* above will be allowed to compile, but the third (which represents a likely bug) will not:
* </p>
*
* <pre class="stREPL">
* scala> import org.scalactic._
* import org.scalactic._
*
* scala> import TripleEquals._
* import TripleEquals._
*
* scala> 1L === 1
* res0: Boolean = true
*
* scala> List(1, 2, 3) === Vector(1, 2, 3)
* res1: Boolean = true
*
* scala> "hi" === 1 // SuperSafe catches the bug at compile time
* <console>:17: error: [Artima SuperSafe] Values of type String and Int may not be compared with
* the === operator. If you really want to compare them for equality, configure Artima SuperSafe to allow
* those types to be compared for equality. For more information on this kind of error, see:
* http://www.artima.com/supersafe_user_guide.html#safer-equality
* "hi" === 1
* ^
* </pre>
*
* <p>
* By contrast, <code>TypeCheckedTripleEquals</code> will prevent any of the above three expressions from compiling:
* </p>
*
* <pre class="stREPL">
* scala> import org.scalactic._
* import org.scalactic._
*
* scala> import TypeCheckedTripleEquals._
* import TypeCheckedTripleEquals._
*
* scala> 1L === 1
* <console>:17: error: types Long and Int do not adhere to the type constraint selected for
* the === and !== operators; the missing implicit parameter is of type org.scalactic.CanEqual[Long,Int]
* 1L === 1
* ^
*
* scala> List(1, 2, 3) === Vector(1, 2, 3)
* <console>:17: error: types List[Int] and scala.collection.immutable.Vector[Int] do not adhere
* to the type constraint selected for the === and !== operators; the missing implicit parameter is
* of type org.scalactic.CanEqual[List[Int],scala.collection.immutable.Vector[Int]]
* List(1, 2, 3) === Vector(1, 2, 3)
* ^
*
* scala> "hi" === 1
* <console>:17: error: types String and Int do not adhere to the type constraint selected for
* the === and !== operators; the missing implicit parameter is of type org.scalactic.CanEqual[String,Int]
* "hi" === 1
* ^
* </pre>
*
* <p>
* Trait <code>TypeCheckedTripleEquals</code> rejects comparisons of types <code>Int</code> and <code>Long</code>, <code>List[Int]</code>
* and <code>Vector[Int]</code>, and <code>String</code> and <code>Int</code>, because none have a direct subtype/supertype relationship.
* To compare two types that are unrelated by inheritance under <code>TypeCheckedTripleEquals</code>, you could
* convert one of them to the other type (because a type is both a subtype and supertype of itself). Here's an example:
* </p>
*
* <pre class="stREPL">
* scala> 1L === 1.toLong // Now both sides are Long
* res0: Boolean = true
*
* scala> List(1, 2, 3) === Vector(1, 2, 3).toList // Now both sides are List[Int]
* res1: Boolean = true
* </pre>
*
* <p>
* Nevertheless, a better (and the recommended) way to deal with this situation is to use a <em>widening type ascription</em>.
* A type ascription is simply a colon and a type placed next to a variable, usually surrounded by parentheses.
* For example, because <code>AnyVal</code> is a common supertype of <code>Int</code> and <code>Long</code>,
* you could solve the type error by widening the type of one side or the other to <code>AnyVal</code>.
* Because <code>AnyVal</code> is a supertype of both <code>Int</code> and <code>Long</code>, the
* type constraint will be satisfied:
* </p>
*
* <pre class="stREPL">
* scala> 1 === (1L: AnyVal)
* res2: Boolean = true
*
* scala> (1: AnyVal) === 1L
* res3: Boolean = true
* </pre>
*
* <p>
* Similarly, since <code>Seq[Int]</code> is a common supertype of both
* <code>Vector[Int]</code> and <code>List[Int]</code>, the type constraint can be
* satisfied by widening either to their common supertype, <code>Seq[Int]</code>:
* </p>
*
* <pre class="stREPL">
* scala> List(1, 2, 3) === (Vector(1, 2, 3): Seq[Int])
* res4: Boolean = true
*
* scala> (List(1, 2, 3): Seq[Int]) === Vector(1, 2, 3)
* res5: Boolean = true
* </pre>
*
* <p>
* The primary intended use case for <code>TypeCheckedTripleEquals</code> is to enable tests to be very strict
* about which types can compared for equality, but it can also be used with production code where this level of
* strictness is desired.
* </p>
*
* <h2>Forcing implicit conversions before equality checks</h2>
*
* <p>
* You can also use a type ascription to force an implicit conversion before a value participates
* in an equality comparison. For example, although <code>JavaConversions</code> provides
* an implicit conversion between <code>java.util.Set</code> and <code>scala.collection.mutable.Set</code>,
* under <code>TypeCheckedTripleEquals</code> an equality comparison between those two types
* will not be allowed:
* </p>
*
* <pre class="stREPL">
* scala> import collection.JavaConversions._
* import collection.JavaConversions._
*
* scala> import collection.mutable
* import collection.mutable
*
* scala> import TypeCheckedTripleEquals._
* import TypeCheckedTripleEquals._
*
* scala> mutable.Set.empty[String] === new java.util.HashSet[String]
* <console>:36: error: types scala.collection.mutable.Set[String] and java.util.HashSet[String] do not
* adhere to the type constraint selected for the === and !== operators; the missing implicit parameter
* is of type org.scalactic.CanEqual[scala.collection.mutable.Set[String],java.util.HashSet[String]]
* mutable.Set.empty[String] === new java.util.HashSet[String]
* ^
* </pre>
*
* <p>
* To force an implicit conversion of the Java <code>HashSet</code> to a Scala <code>mutable.Set</code>, after which the
* type constraint will be satisfied, you can use a type ascription:
* </p>
*
* <pre class="stREPL">
* scala> mutable.Set.empty[String] === (new java.util.HashSet[String]: mutable.Set[String])
* res0: Boolean = true
* </pre>
*
* <h2>Scoping equality policies</h2>
*
* <p>
* This trait will override or hide implicit methods defined by
* <a href="TripleEquals.html"><code>TripleEquals</code></a>
* and can therefore be used to temporarily turn on or off type checking in a limited scope. Here's an example, in which <code>TypeCheckedTripleEquals</code> will
* cause a compiler error:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">import</span> org.scalactic._
* <span class="stReserved">import</span> TypeCheckedTripleEquals._
* <br/><span class="stReserved">object</span> <span class="stType">Example</span> {
* <br/> <span class="stReserved">def</span> cmp(a: <span class="stType">Int</span>, b: <span class="stType">Long</span>): <span class="stType">Int</span> = {
* <span class="stReserved">if</span> (a === b) <span class="stLiteral">0</span> <span class="stLineComment">// This line won't compile</span>
* <span class="stReserved">else</span> <span class="stReserved">if</span> (a < b) -<span class="stLiteral">1</span>
* <span class="stReserved">else</span> <span class="stLiteral">1</span>
* }
* <br/> <span class="stReserved">def</span> cmp(s: <span class="stType">String</span>, t: <span class="stType">String</span>): <span class="stType">Int</span> = {
* <span class="stReserved">if</span> (s === t) <span class="stLiteral">0</span>
* <span class="stReserved">else</span> <span class="stReserved">if</span> (s < t) -<span class="stLiteral">1</span>
* <span class="stReserved">else</span> <span class="stLiteral">1</span>
* }
* }
* </pre>
*
* <p>
* Because <code>Int</code> and <code>Long</code> are not in a subtype/supertype relationship, comparing <code>1</code> and <code>1L</code> in the context
* of <code>TypeCheckedTripleEquals</code> will generate a compiler error:
* </p>
*
* <pre>
* Example.scala:9: error: types Int and Long do not adhere to the type constraint selected
* for the === and !== operators; the missing implicit parameter is of
* type org.scalactic.CanEqual[Int,Long]
* if (a === b) 0 // This line won't compile
* ^
* one error found
* </pre>
*
* <p>
* You can “relax” the type checking locally by importing
* the members of <code>TripleEquals</code> in a limited scope:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">package</span> org.scalactic.examples.conversioncheckedtripleequals
* <br/><span class="stReserved">import</span> org.scalactic._
* <span class="stReserved">import</span> TypeCheckedTripleEquals._
* <br/><span class="stReserved">object</span> <span class="stType">Example</span> {
* <br/> <span class="stReserved">def</span> cmp(a: <span class="stType">Int</span>, b: <span class="stType">Long</span>): <span class="stType">Int</span> = {
* <span class="stReserved">import</span> TripleEquals._
* <span class="stReserved">if</span> (a === b) <span class="stLiteral">0</span>
* <span class="stReserved">else</span> <span class="stReserved">if</span> (a < b) -<span class="stLiteral">1</span>
* <span class="stReserved">else</span> <span class="stLiteral">1</span>
* }
* <br/> <span class="stReserved">def</span> cmp(s: <span class="stType">String</span>, t: <span class="stType">String</span>): <span class="stType">Int</span> = {
* <span class="stReserved">if</span> (s === t) <span class="stLiteral">0</span>
* <span class="stReserved">else</span> <span class="stReserved">if</span> (s < t) -<span class="stLiteral">1</span>
* <span class="stReserved">else</span> <span class="stLiteral">1</span>
* }
* }
* </pre>
*
* <p>
* With the above change, the <code>Example.scala</code> file compiles fine. The strict checking is disabled only inside the first <code>cmp</code> method that
* takes an <code>Int</code> and a <code>Long</code>. <code>TypeCheckedTripleEquals</code> is still enforcing its type constraint, for example, for the <code>s === t</code>
* expression in the other overloaded <code>cmp</code> method that takes strings.
* </p>
*
* <p>
* Because the methods <code>TripleEquals</code> and <code>TypeCheckedTripleEquals</code>
* <em>override</em> all the methods defined in supertype <a href="TripleEqualsSupport.html"><code>TripleEqualsSupport</code></a>, you can achieve the same
* kind of nested tuning of equality constraints whether you mix in traits, import from companion objects, or use some combination of both.
* </p>
*
* <p>
* In short, you should be able to select a primary constraint level via either a mixin or import, then change that in nested scopes
* however you want, again either through a mixin or import, without getting any implicit conversion ambiguity. The innermost constraint level in scope
* will always be in force.
* </p>
*
* @author Bill Venners
*/
trait TypeCheckedTripleEquals extends LowPriorityTypeCheckedConstraint {
import scala.language.implicitConversions
// Inherit the Scaladoc for these methods
override def convertToEqualizer[T](left: T): Equalizer[T] = new Equalizer(left)
implicit override def convertToCheckingEqualizer[T](left: T): CheckingEqualizer[T] = new CheckingEqualizer(left)
override def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): A CanEqual B = new EqualityConstraint[A, B](equalityOfA)
implicit override def typeCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], ev: B <:< A): A CanEqual B = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev)
implicit override def convertEquivalenceToBToAConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B <:< A): A CanEqual B = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev)
@deprecated("The lowPriorityConversionCheckedConstraint method has been deprecated and will be removed in a future version of ScalaTest. It is no longer needed now that the deprecation period of ConversionCheckedTripleEquals has expired. It will not be replaced.", "3.1.0")
override def lowPriorityConversionCheckedConstraint[A, B](implicit equivalenceOfB: Equivalence[B], cnv: A => B): A CanEqual B = new AToBEquivalenceConstraint[A, B](equivalenceOfB, cnv)
@deprecated("The convertEquivalenceToAToBConversionConstraint method has been deprecated and will be removed in a future version of ScalaTest. It is no longer needed now that the deprecation period of ConversionCheckedTripleEquals has expired. It will not be replaced.", "3.1.0")
override def convertEquivalenceToAToBConversionConstraint[A, B](equivalenceOfB: Equivalence[B])(implicit ev: A => B): A CanEqual B = new AToBEquivalenceConstraint[A, B](equivalenceOfB, ev)
@deprecated("The conversionCheckedConstraint method has been deprecated and will be removed in a future version of ScalaTest. It is no longer needed now that the deprecation period of ConversionCheckedTripleEquals has expired. It will not be replaced.", "3.1.0")
override def conversionCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], cnv: B => A): A CanEqual B = new BToAEquivalenceConstraint[A, B](equivalenceOfA, cnv)
@deprecated("The convertEquivalenceToBToAConversionConstraint method has been deprecated and will be removed in a future version of ScalaTest. It is no longer needed now that the deprecation period of ConversionCheckedTripleEquals has expired. It will not be replaced.", "3.1.0")
override def convertEquivalenceToBToAConversionConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B => A): A CanEqual B = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev)
}
/**
* Companion object to trait <code>TypeCheckedTripleEquals</code> that facilitates the importing of <code>TypeCheckedTripleEquals</code> members as
* an alternative to mixing it in. One use case is to import <code>TypeCheckedTripleEquals</code> members so you can use
* them in the Scala interpreter:
*
* <pre class="stREPL">
* $ scala -classpath scalatest.jar
* Welcome to Scala version 2.10.0
* Type in expressions to have them evaluated.
* Type :help for more information.
*
* scala> import org.scalactic._
* import org.scalactic._
*
* scala> import TypeCheckedTripleEquals._
* import TypeCheckedTripleEquals._
*
* scala> 1 + 1 === 2
* res0: Boolean = true
* </pre>
*/
object TypeCheckedTripleEquals extends TypeCheckedTripleEquals