Skip to content

Rename Not to NotGiven #10533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand All @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions docs/docs/reference/contextual/givens.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ 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. Example:

```scala

import scala.util.NotGiven

trait Tagged[A]

case class Foo[A](value: Boolean)
trait FooLowPrio {
implicit def fooDefault[A]: Foo[A] = Foo(true)
}

object Foo extends FooLowPrio {
implicit def fooNotTagged[A](implicit ev: NotGiven[Tagged[A]]): Foo[A] = Foo(false)
}

@main def main() =
given Tagged[Int] = null

assert(implicitly[Foo[Int]].value) // fooDefault

assert(!implicitly[Foo[String]].value) // fooNotTagged

```

## Given Instance Initialization

A given instance without type or context parameters is initialized on-demand, the first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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] as Not[T] = Not.value
given default[T] as 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) as Not[T] = ???
given amb1[T](using ev: T) as NotGiven[T] = ???

/** One of two ambiguous methods used to emulate negation in Scala 2 */
given amb2[T](using ev: T) as Not[T] = ???
given amb2[T](using ev: T) as NotGiven[T] = ???
}
4 changes: 2 additions & 2 deletions tests/neg-macros/i9972/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion tests/neg-macros/i9972b/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

def test: Unit =
summon[scala.util.Not[T[Int]]] // error
summon[scala.util.NotGiven[T[Int]]] // error
6 changes: 3 additions & 3 deletions tests/neg/i5234a.scala
Original file line number Diff line number Diff line change
@@ -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]]
}
20 changes: 10 additions & 10 deletions tests/neg/i5234b.scala
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
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 {
class Foo
class Bar
implicit def foo: Foo = ???
implicitly[Foo]
implicitly[Not2[Foo]] // error
implicitly[Not2[Bar]]
implicitly[NotGiven2[Foo]] // error
implicitly[NotGiven2[Bar]]
}
4 changes: 2 additions & 2 deletions tests/neg/i5234c.scala
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 2 additions & 2 deletions tests/run/i3396.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import scala.util.Not
import scala.util.NotGiven

object Test {

Expand All @@ -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)
}


Expand Down