org.scalautils.ConversionCheckedTripleEquals.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.11.0-M5 Show documentation
/* * 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.scalautils import TripleEqualsSupport._ /** * Provides
TypeCheckedTripleEquals, it will also compile * type types that would be rejected by===
and!==
operators that returnBoolean
, delegate the equality determination * to anEquality
type class, and require that either the types of the two values compared are in a subtype/supertype * relationship, or that an implicit conversion is available that can convert from one type to the other. * ** *
* Recommended Usage: * Trait ConversionCheckedTripleEquals
is useful (in both production and test code) when you need determine equality for a type of object differently than * itsequals
* method—either you can't change theequals
method, or theequals
method is sensible generally, but you're in a special situation where you * need something else—and/or you want a compile-time type check that allows types that are implicitly convertable in either (or both) directions. ** This trait is the middle ground of the three triple equals traits, in between *
TripleEquals
, the most lenient, andTypeCheckedTripleEquals
, the most strict. * IfTripleEquals
is mixed in or imported, the===
can be used with any two types * and still compile. IfTypeCheckedTripleEquals
is mixed in or imported, however, only types in * a subtype or supertype relationship with each other (including when both types are exactly the same) will compile. *ConversionCheckedTripleEquals
is slightly more accomodating, because in addition to compiling any * use of===
that will compile underTypeCheckedTripleEquals
, so long as an implicit * conversion (in either direction) from one type to another is available. * * ** For example, under
* *TypeCheckedTripleEquals
, the following use of===
will not compile, * becauseInt
andLong
are not in a subtype/supertype relationship. (I.e.,Int
* is not a subtype or supertype ofLong
): ** scala> import org.scalautils._ * import org.scalautils._ * * scala> import TypeCheckedTripleEquals._ * import TypeCheckedTripleEquals._ * * scala> 1 === 1L * <console>:14: error: types Int and Long do not adhere to the equality constraint selected for * the === and !== operators; they must either be in a subtype/supertype relationship, or, if * ConversionCheckedTripleEquals is in force, implicitly convertible in one direction or the other; * the missing implicit parameter is of type org.scalautils.Constraint[Int,Long] * 1 === 1L * ^ ** ** Trait
* *TypeCheckedTripleEquals
rejects typesInt
andLong
because they are not directly related via * subtyping. However, an implicit widening conversion fromInt
to Long does exist (imported implicitly from *scala.Predef
), soConversionCheckedTripleEquals
* will allow it: ** scala> import ConversionCheckedTripleEquals._ * import ConversionCheckedTripleEquals._ * * scala> 1 === 1L * res1: Boolean = true ** ** The implicit conversion can go in either direction: from the left type to the right type, or vice versa. In the above expression the * implicit conversion goes from left to right (the
* *Int
on the left to theLong
on the right). It also works * the other way: ** scala> 1L === 1 * res2: Boolean = true ** ** This trait will override or hide implicit methods defined by its sibling traits, *
* *TripleEquals
orTypeCheckedTripleEquals
, * and can therefore be used to temporarily turn on or off conversion checking in a limited scope. Here's an example, in whichTypeCheckedTripleEquals
will * cause a compiler error: ** import org.scalautils._ * import TypeCheckedTripleEquals._ * * object Example { * * def cmp(a: Int, b: Long): Int = { * if (a === b) 0 // This line won't compile * else if (a < b) -1 * else 1 * } * * def cmp(s: String, t: String): Int = { * if (s === t) 0 * else if (s < t) -1 * else 1 * } * } ** * BecauseInt
andLong
are not in a subtype/supertype relationship, comparing1
and1L
in the context * ofTypeCheckedTripleEquals
will generate a compiler error: * * ** Example.scala:9: error: types Int and Long do not adhere to the equality constraint selected for * the === and !== operators; they must either be in a subtype/supertype relationship, or, if * ConversionCheckedTripleEquals is in force, implicitly convertible in one direction or the other; * the missing implicit parameter is of type org.scalautils.Constraint[Int,Long] * if (a === b) 0 // This line won't compile * ^ * one error found ** ** You can “relax” the type checking (i.e., by additionally allowing implicitly convertible types) locally by importing * the members of
* *ConversionCheckedTripleEquals
in a limited scope: ** package org.scalautils.examples.conversioncheckedtripleequals * * import org.scalautils._ * import TypeCheckedTripleEquals._ * * object Example { * * def cmp(a: Int, b: Long): Int = { * import ConversionCheckedTripleEquals._ * if (a === b) 0 * else if (a < b) -1 * else 1 * } * * def cmp(s: String, t: String): Int = { * if (s === t) 0 * else if (s < t) -1 * else 1 * } * } ** ** With the above change, the
* *Example.scala
file compiles fine. Conversion checking is enabled only inside the firstcmp
method that * takes anInt
and aLong
.TypeCheckedTripleEquals
is still enforcing its type constraint, for example, for thes === t
* expression in the other overloadedcmp
method that takes strings. ** Because the methods in
* *ConversionCheckedTripleEquals
(and its siblings) * override all the methods defined in supertypeTripleEqualsSupport
, 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. ** 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. *
* *
* An alternative way to solve an unwanted compiler error caused by an over-zealous type constraint is with a widening type ascription. Here * are some examples: *
* ** scala> import org.scalautils._ * import org.scalautils._ * * scala> import ConversionCheckedTripleEquals._ * import ConversionCheckedTripleEquals._ * * scala> List(1, 2, 3) === Vector(1, 2, 3) * <console>:14: error: types List[Int] and scala.collection.immutable.Vector[Int] do not adhere to the equality constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalautils.Constraint[List[Int],scala.collection.immutable.Vector[Int]] * List(1, 2, 3) === Vector(1, 2, 3) * ^ ** ** Although you could solve the above type error with
* *TraversableEqualityConstraints
, you could also * simply widen the type of one side or the other toAny
. BecauseAny
is a supertype of everything, the * type constraint will be satisfied: ** scala> List(1, 2, 3) === (Vector(1, 2, 3): Any) * res1: Boolean = true * * scala> (List(1, 2, 3): Any) === Vector(1, 2, 3) * res2: Boolean = true ** ** You could alternatively widen a type to a more specific common supertype than
* *Any
. For example, sinceList[Int]
and *Vector[Int]
are both subtypes ofSeq[Int]
, so you could widen either type toSeq[Int]
to satisfy * the type checker: ** scala> List(1, 2, 3) === (Vector(1, 2, 3): Seq[Int]) * res3: Boolean = true * * scala> (List(1, 2, 3): Seq[Int]) === Vector(1, 2, 3) * res4: Boolean = true ** * @author Bill Venners */ trait ConversionCheckedTripleEquals extends LowPriorityConversionCheckedConstraint { // 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 convertToLegacyEqualizer[T](left: T): LegacyEqualizer[T] = new LegacyEqualizer(left) override def convertToLegacyCheckingEqualizer[T](left: T): LegacyCheckingEqualizer[T] = new LegacyCheckingEqualizer(left) override def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): Constraint[A, B] = new EqualityConstraint[A, B](equalityOfA) override def lowPriorityTypeCheckedConstraint[A, B](implicit equivalenceOfB: Equivalence[B], ev: A <:< B): Constraint[A, B] = new AToBEquivalenceConstraint[A, B](equivalenceOfB, ev) override def convertEquivalenceToAToBConstraint[A, B](equivalenceOfB: Equivalence[B])(implicit ev: A <:< B): Constraint[A, B] = new AToBEquivalenceConstraint[A, B](equivalenceOfB, ev) override def typeCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], ev: B <:< A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev) override def convertEquivalenceToBToAConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B <:< A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev) implicit override def conversionCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], cnv: B => A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, cnv) implicit override def convertEquivalenceToBToAConversionConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B => A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev) } /** * Companion object to traitConversionCheckedTripleEquals
that facilitates the importing ofConversionCheckedTripleEquals
members as * an alternative to mixing it in. One use case is to importConversionCheckedTripleEquals
members so you can use * them in the Scala interpreter: * ** $ scala -classpath scalautils.jar * Welcome to Scala version 2.10.0 * Type in expressions to have them evaluated. * Type :help for more information. * * scala> import org.scalautils._ * import org.scalautils._ * * scala> import ConversionCheckedTripleEquals._ * import ConversionCheckedTripleEquals._ * * scala> 1 === 1L * res0: Boolean = true **/ object ConversionCheckedTripleEquals extends ConversionCheckedTripleEquals