diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f0d9582ef9fe..9e1a3d9a2baa 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -851,8 +851,8 @@ class Definitions { @tu lazy val TypeBox_CAP: TypeSymbol = TypeBoxClass.requiredType(tpnme.CAP) @tu lazy val MatchCaseClass: ClassSymbol = requiredClass("scala.runtime.MatchCase") - @tu lazy val NotClass: ClassSymbol = requiredClass("scala.util.Not") - @tu lazy val Not_value: Symbol = NotClass.companionModule.requiredMethod(nme.value) + @tu lazy val NotGivenClass: ClassSymbol = requiredClass("scala.util.NotGiven") + @tu lazy val NotGiven_value: Symbol = NotGivenClass.companionModule.requiredMethod(nme.value) @tu lazy val ValueOfClass: ClassSymbol = requiredClass("scala.ValueOf") diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 03a81f42745b..61eb32bb11f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1072,7 +1072,7 @@ trait Implicits: else ViewProto(wildApprox(argument.tpe.widen), wildApprox(pt)) // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. - val isNot: Boolean = wildProto.classSymbol == defn.NotClass + val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass /** Try to type-check implicit reference, after checking that this is not * a diverging search @@ -1201,10 +1201,10 @@ trait Implicits: } def negateIfNot(result: SearchResult) = - if (isNot) + if (isNotGiven) result match { case _: SearchFailure => - SearchSuccess(ref(defn.Not_value), defn.Not_value.termRef, 0)( + SearchSuccess(ref(defn.NotGiven_value), defn.NotGiven_value.termRef, 0)( ctx.typerState.fresh().setCommittable(true), ctx.gadt ) @@ -1220,7 +1220,7 @@ trait Implicits: |According to the new implicit resolution rules this is no longer possible; |the search will fail with a global ambiguity error instead. | - |Consider using the scala.util.Not class to implement similar functionality.""", + |Consider using the scala.util.NotGiven class to implement similar functionality.""", ctx.source.atSpan(span)) /** A relation that influences the order in which implicits are tried. diff --git a/docs/docs/reference/contextual/givens.md b/docs/docs/reference/contextual/givens.md index b7670698ee68..b63607dfb68c 100644 --- a/docs/docs/reference/contextual/givens.md +++ b/docs/docs/reference/contextual/givens.md @@ -106,6 +106,29 @@ instance named `ctx` is established by matching against the first half of the `p In each case, a pattern-bound given instance consists of `given` and a type `T`. The pattern matches exactly the same selectors as the type ascription pattern `_: T`. +## Negated Givens + +Scala 2's somewhat puzzling behavior with respect to ambiguity has been exploited to implement the analogue of a "negated" search in implicit resolution, where a query Q1 fails if some other query Q2 succeeds and Q1 succeeds if Q2 fails. With the new cleaned up behavior these techniques no longer work. But there is now a new special type `scala.util.NotGiven` which implements negation directly. + +For any query type `Q`, `NotGiven[Q]` succeeds if and only if the implicit +search for `Q` fails, for example: + +```scala +import scala.util.NotGiven + +trait Tagged[A] + +case class Foo[A](value: Boolean) +object Foo: + given fooTagged[A](using Tagged[A]): Foo[A] = Foo(true) + given fooNotTagged[A](using NotGiven[Tagged[A]]): Foo[A] = Foo(false) + +@main def test() = + given Tagged[Int] with {} + assert(implicitly[Foo[Int]].value) // fooTagged is found + assert(!implicitly[Foo[String]].value) // fooNotTagged is found +``` + ## Given Instance Initialization A given instance without type or context parameters is initialized on-demand, the first diff --git a/library/src/scala/util/Not.scala b/library/src/scala/util/NotGiven.scala similarity index 60% rename from library/src/scala/util/Not.scala rename to library/src/scala/util/NotGiven.scala index 849bd32dad71..99cc903d4426 100644 --- a/library/src/scala/util/Not.scala +++ b/library/src/scala/util/NotGiven.scala @@ -8,38 +8,38 @@ package scala.util * putting them in different traits we can instead define the following: * * given i1: D(using ev: C) = ... - * given i2: D(using ev: Not[C]) = ... + * given i2: D(using ev: NotGiven[C]) = ... * - * `Not` is treated specially in implicit search, similar to the way logical negation - * is treated in Prolog: The implicit search for `Not[C]` succeeds if and only if the implicit + * `NotGiven` is treated specially in implicit search, similar to the way logical negation + * is treated in Prolog: The implicit search for `NotGiven[C]` succeeds if and only if the implicit * search for `C` fails. * * In Scala 2 this form of negation can be simulated by setting up a conditional * ambiguous implicit and an unconditional fallback, the way it is done with the * `default`, `amb1` and `amb2` methods below. Due to the way these two methods are - * defined, `Not` is also usable from Scala 2. + * defined, `NotGiven` is also usable from Scala 2. * * In Dotty, ambiguity is a global error, and therefore cannot be used to implement negation. - * Instead, `Not` is treated natively in implicit search. + * Instead, `NotGiven` is treated natively in implicit search. */ -final class Not[+T] private () +final class NotGiven[+T] private () -trait LowPriorityNot { +trait LowPriorityNotGiven { /** A fallback method used to emulate negation in Scala 2 */ - given default[T]: Not[T] = Not.value + given default[T]: NotGiven[T] = NotGiven.value } -object Not extends LowPriorityNot { +object NotGiven extends LowPriorityNotGiven { - /** A value of type `Not` to signal a successful search for `Not[C]` (i.e. a failing + /** A value of type `NotGiven` to signal a successful search for `NotGiven[C]` (i.e. a failing * search for `C`). A reference to this value will be explicitly constructed by Dotty's * implicit search algorithm */ - def value: Not[Nothing] = new Not[Nothing]() + def value: NotGiven[Nothing] = new NotGiven[Nothing]() /** One of two ambiguous methods used to emulate negation in Scala 2 */ - given amb1[T](using ev: T): Not[T] = ??? + given amb1[T](using ev: T): NotGiven[T] = ??? /** One of two ambiguous methods used to emulate negation in Scala 2 */ - given amb2[T](using ev: T): Not[T] = ??? + given amb2[T](using ev: T): NotGiven[T] = ??? } diff --git a/tests/neg-macros/i9972/Macro_1.scala b/tests/neg-macros/i9972/Macro_1.scala index 65e69f8ccab3..8b7a82e3b841 100644 --- a/tests/neg-macros/i9972/Macro_1.scala +++ b/tests/neg-macros/i9972/Macro_1.scala @@ -1,7 +1,7 @@ package notmacro -import scala.util.Not +import scala.util.NotGiven object Main extends App { - summon[Not[T[Int]]] // error + summon[NotGiven[T[Int]]] // error } diff --git a/tests/neg-macros/i9972b/Macro_1.scala b/tests/neg-macros/i9972b/Macro_1.scala index ecb22a0b3755..38be5cf05153 100644 --- a/tests/neg-macros/i9972b/Macro_1.scala +++ b/tests/neg-macros/i9972b/Macro_1.scala @@ -1,3 +1,3 @@ def test: Unit = - summon[scala.util.Not[T[Int]]] // error + summon[scala.util.NotGiven[T[Int]]] // error diff --git a/tests/neg/i5234a.scala b/tests/neg/i5234a.scala index 389b215fe45b..88e20846400a 100644 --- a/tests/neg/i5234a.scala +++ b/tests/neg/i5234a.scala @@ -1,10 +1,10 @@ object Test { - import scala.util.Not + import scala.util.NotGiven class Foo class Bar implicit def foo: Foo = ??? implicitly[Foo] - implicitly[Not[Foo]] // error - implicitly[Not[Bar]] + implicitly[NotGiven[Foo]] // error + implicitly[NotGiven[Bar]] } diff --git a/tests/neg/i5234b.scala b/tests/neg/i5234b.scala index 2dd1d679336f..cae4cc6f16ff 100644 --- a/tests/neg/i5234b.scala +++ b/tests/neg/i5234b.scala @@ -1,23 +1,23 @@ -final class Not2[T] private () +final class NotGiven2[T] private () -trait LowPriorityNot2 { +trait LowPriorityNotGiven2 { /** A fallback method used to emulate negation in Scala 2 */ - implicit def default[T]: Not2[T] = Not2.value.asInstanceOf[Not2[T]] + implicit def default[T]: NotGiven2[T] = NotGiven2.value.asInstanceOf[NotGiven2[T]] } -object Not2 extends LowPriorityNot2 { +object NotGiven2 extends LowPriorityNotGiven2 { - /** A value of type `Not` to signal a successful search for `Not[C]` (i.e. a failing + /** A value of type `NotGiven` to signal a successful search for `NotGiven[C]` (i.e. a failing * search for `C`). A reference to this value will be explicitly constructed by * Dotty's implicit search algorithm */ - def value: Not2[Nothing] = new Not2[Nothing]() + def value: NotGiven2[Nothing] = new NotGiven2[Nothing]() /** One of two ambiguous methods used to emulate negation in Scala 2 */ - implicit def amb1[T](implicit ev: T): Not2[T] = ??? + implicit def amb1[T](implicit ev: T): NotGiven2[T] = ??? /** One of two ambiguous methods used to emulate negation in Scala 2 */ - implicit def amb2[T](implicit ev: T): Not2[T] = ??? + implicit def amb2[T](implicit ev: T): NotGiven2[T] = ??? } object Test { @@ -25,6 +25,6 @@ object Test { class Bar implicit def foo: Foo = ??? implicitly[Foo] - implicitly[Not2[Foo]] // error - implicitly[Not2[Bar]] + implicitly[NotGiven2[Foo]] // error + implicitly[NotGiven2[Bar]] } diff --git a/tests/neg/i5234c.scala b/tests/neg/i5234c.scala index 59a7c605c65e..0d9754f85788 100644 --- a/tests/neg/i5234c.scala +++ b/tests/neg/i5234c.scala @@ -1,9 +1,9 @@ object Test { - import scala.util.Not + import scala.util.NotGiven class Foo implicit def foo: Foo = ??? - def foo[T](implicit ev: Not[T]) = ??? + def foo[T](implicit ev: NotGiven[T]) = ??? foo[Foo] // error } diff --git a/tests/run/i3396.scala b/tests/run/i3396.scala index eea4371e0636..8f10e7bcfcbd 100644 --- a/tests/run/i3396.scala +++ b/tests/run/i3396.scala @@ -1,4 +1,4 @@ -import scala.util.Not +import scala.util.NotGiven object Test { @@ -9,7 +9,7 @@ object Test { implicit def fooDefault[A]: Foo[A] = Foo(true) } object Foo extends FooLowPrio { - implicit def fooNotTagged[A](implicit ev: Not[Tagged[A]]): Foo[A] = Foo(false) + implicit def fooNotTagged[A](implicit ev: NotGiven[Tagged[A]]): Foo[A] = Foo(false) }