Skip to content

Commit 2144367

Browse files
committed
Implement negation of implicit searches
Previously, ambiguous implicits were used to make a local implicit search fail so that another alternative could be considered globally. This is no longer possible because implicit search now propagates. This commit makes similar functionality available by implementing a special Not class. It also re-establishes the old behavior under -language:Scala2, issuing a migration warning.
1 parent 0bae8a3 commit 2144367

File tree

5 files changed

+104
-6
lines changed

5 files changed

+104
-6
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,12 @@ class Definitions {
584584

585585
def Eq_eqAny(implicit ctx: Context) = EqModule.requiredMethod(nme.eqAny)
586586

587+
lazy val NotType = ctx.requiredClassRef("scala.implicits.Not")
588+
def NotClass(implicit ctx: Context) = NotType.symbol.asClass
589+
def NotModule(implicit ctx: Context) = NotClass.companionModule
590+
591+
def Not_value(implicit ctx: Context) = NotModule.requiredMethod(nme.value)
592+
587593
lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")
588594

589595
// Annotation base classes

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ object Implicits {
262262
case _: SearchSuccess => this
263263
case fail: SearchFailure => other(fail)
264264
}
265+
def isSuccess = isInstanceOf[SearchSuccess]
265266
}
266267

267268
/** A successful search
@@ -503,8 +504,7 @@ trait Implicits { self: Typer =>
503504
&& from.isValueType
504505
&& ( from.isValueSubType(to)
505506
|| inferView(dummyTreeOfType(from), to)
506-
(ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState())
507-
.isInstanceOf[SearchSuccess]
507+
(ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()).isSuccess
508508
// TODO: investigate why we can't TyperState#test here
509509
)
510510
)
@@ -583,7 +583,7 @@ trait Implicits { self: Typer =>
583583
}
584584

585585
def hasEq(tp: Type): Boolean =
586-
inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isInstanceOf[SearchSuccess]
586+
inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isSuccess
587587

588588
def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = {
589589
List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos))
@@ -787,6 +787,8 @@ trait Implicits { self: Typer =>
787787
/** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */
788788
val wildProto = implicitProto(pt, wildApprox(_, null, Set.empty))
789789

790+
val isNot = wildProto.classSymbol == defn.NotClass
791+
790792
/** Search a list of eligible implicit references */
791793
def searchImplicits(eligible: List[Candidate], contextual: Boolean): SearchResult = {
792794
val constr = ctx.typerState.constraint
@@ -896,10 +898,23 @@ trait Implicits { self: Typer =>
896898
case cand :: pending1 =>
897899
tryImplicit(cand) match {
898900
case fail: SearchFailure =>
899-
if (fail.isAmbiguous) healAmbiguous(pending1, fail)
900-
else rank(pending1, found, fail :: rfailures)
901+
if (isNot)
902+
SearchSuccess(ref(defn.Not_value), defn.Not_value.termRef, 0)(ctx.typerState)
903+
else if (fail.isAmbiguous)
904+
if (ctx.scala2Mode) {
905+
val result = rank(pending1, found, NoMatchingImplicitsFailure :: rfailures)
906+
if (result.isSuccess)
907+
warnAmbiguousNegation(fail.reason.asInstanceOf[AmbiguousImplicits])
908+
result
909+
}
910+
else
911+
healAmbiguous(pending1, fail)
912+
else
913+
rank(pending1, found, fail :: rfailures)
901914
case best: SearchSuccess =>
902-
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
915+
if (isNot)
916+
NoMatchingImplicitsFailure
917+
else if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
903918
best
904919
else disambiguate(found, best) match {
905920
case retained: SearchSuccess =>
@@ -917,6 +932,15 @@ trait Implicits { self: Typer =>
917932
else found.recoverWith(_ => rfailures.reverse.maxBy(_.tree.treeSize))
918933
}
919934

935+
def warnAmbiguousNegation(ambi: AmbiguousImplicits) =
936+
ctx.migrationWarning(
937+
i"""Ambiguous implicits ${ambi.alt1.ref.symbol.showLocated} and ${ambi.alt2.ref.symbol.showLocated}
938+
|seem to be used to implement a local failure in order to negate an implicit search.
939+
|According to the new implicit resolution rules this is no longer possible;
940+
|the search will fail with a global ambiguity error instead.
941+
|
942+
|Consider using the scala.implicits.Not class to implement similar functionality.""", pos)
943+
920944
/** A relation that imfluences the order in which implicits are tried.
921945
* We prefer (in order of importance)
922946
* 1. more deeply nested definitions

library/src/scala/implicits/Not.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package scala.implicits
2+
3+
final class Not[+T] private ()
4+
5+
object Not {
6+
def value: Not[Nothing] = new Not[Nothing]()
7+
implicit def amb1[T](implicit ev: T): Not[T] = ???
8+
implicit def amb2[T](implicit ev: T): Not[T] = ???
9+
}

tests/pos-scala2/i3396.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
object Test {
2+
3+
trait Tagged[A]
4+
5+
// Negation Tagged: NotTagged[A] is available only if there are no Tagged[A] in scope.
6+
trait NotTagged[A]
7+
trait NotTaggedLowPrio {
8+
implicit def notTaggedInstance[A]: NotTagged[A] = null
9+
}
10+
object NotTagged extends NotTaggedLowPrio {
11+
implicit def notTaggedAmbiguity1[A](implicit ev: Tagged[A]): NotTagged[A] = null
12+
implicit def notTaggedAmbiguity2[A](implicit ev: Tagged[A]): NotTagged[A] = null
13+
}
14+
15+
16+
case class Foo[A](value: Boolean)
17+
trait FooLowPrio {
18+
implicit def fooDefault[A]: Foo[A] = Foo(true)
19+
}
20+
object Foo extends FooLowPrio {
21+
implicit def fooNotTagged[A](implicit ev: NotTagged[A]): Foo[A] = Foo(false)
22+
}
23+
24+
25+
def main(args: Array[String]): Unit = {
26+
implicit val taggedInt: Tagged[Int] = null
27+
28+
assert(implicitly[Foo[Int]].value) // fooDefault
29+
30+
assert(!implicitly[Foo[String]].value) // fooNotTagged
31+
32+
println(1)
33+
}
34+
}

tests/run/i3396.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import implicits.Not
2+
3+
object Test {
4+
5+
trait Tagged[A]
6+
7+
case class Foo[A](value: Boolean)
8+
trait FooLowPrio {
9+
implicit def fooDefault[A]: Foo[A] = Foo(true)
10+
}
11+
object Foo extends FooLowPrio {
12+
implicit def fooNotTagged[A](implicit ev: Not[Tagged[A]]): Foo[A] = Foo(false)
13+
}
14+
15+
16+
def main(args: Array[String]): Unit = {
17+
implicit val taggedInt: Tagged[Int] = null
18+
19+
assert(implicitly[Foo[Int]].value) // fooDefault
20+
21+
assert(!implicitly[Foo[String]].value) // fooNotTagged
22+
23+
println(1)
24+
}
25+
}

0 commit comments

Comments
 (0)