From d9707aacd8f5895a90a6b9b2902d0180b98b24ff Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 8 Sep 2022 18:09:02 +0200 Subject: [PATCH 1/5] Better diagnostics for too unspecific implicit searches Fixes #15998 --- .../tools/dotc/typer/ErrorReporting.scala | 11 +++--- .../dotty/tools/dotc/typer/Implicits.scala | 11 +++--- tests/neg/i15998.check | 36 +++++++++++++++++++ tests/neg/i15998.scala | 21 +++++++++++ 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 tests/neg/i15998.check create mode 100644 tests/neg/i15998.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 3c92a217206d..dbb35c0ab761 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -125,25 +125,25 @@ object ErrorReporting { def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = { val normTp = normalize(tree.tpe, pt) val normPt = normalize(pt, pt) - + def contextFunctionCount(tp: Type): Int = tp.stripped match case defn.ContextFunctionType(_, restp, _) => 1 + contextFunctionCount(restp) case _ => 0 def strippedTpCount = contextFunctionCount(tree.tpe) - contextFunctionCount(normTp) def strippedPtCount = contextFunctionCount(pt) - contextFunctionCount(normPt) - + val (treeTp, expectedTp) = if normTp <:< normPt || strippedTpCount != strippedPtCount then (tree.tpe, pt) else (normTp, normPt) // use normalized types if that also shows an error, and both sides stripped // the same number of context functions. Use original types otherwise. - + def missingElse = tree match case If(_, _, elsep @ Literal(Constant(()))) if elsep.span.isSynthetic => "\nMaybe you are missing an else part for the conditional?" case _ => "" - + errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), implicitFailure.whyNoConversion, missingElse)) } @@ -262,6 +262,9 @@ class ImplicitSearchError( case _ => defaultAmbiguousImplicitMsg(ambi) } + case ambi @ TooUnspecific(target) => + ex"""No implicit search was attempted${location("for")} + |since the expected type $target is too unspecific""" case _ => val shortMessage = userDefinedImplicitNotFoundParamMessage .orElse(userDefinedImplicitNotFoundTypeMessage) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ac86702ac588..ede44c2b7f86 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -411,14 +411,14 @@ object Implicits: /** A failed search */ case class SearchFailure(tree: Tree) extends SearchResult { - final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits] + final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific] final def reason: SearchFailureType = tree.tpe.asInstanceOf[SearchFailureType] } object SearchFailure { def apply(tpe: SearchFailureType, span: Span)(using Context): SearchFailure = { val id = tpe match - case tpe: AmbiguousImplicits => + case tpe: (AmbiguousImplicits | TooUnspecific) => untpd.SearchFailureIdent(nme.AMBIGUOUS, s"/* ambiguous: ${tpe.explanation} */") case _ => untpd.SearchFailureIdent(nme.MISSING, "/* missing */") @@ -504,11 +504,14 @@ object Implicits: SearchFailure(ImplicitSearchTooLarge, NoSpan)(using NoContext) /** A failure value indicating that an implicit search for a conversion was not tried */ - class TooUnspecific(target: Type) extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty): + case class TooUnspecific(target: Type) extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty): override def whyNoConversion(using Context): String = i""" |Note that implicit conversions were not tried because the result of an implicit conversion |must be more specific than $target""" + override def explanation(using Context) = + i"""${super.explanation}. + |The expected type $target is not specific enough, so no search was attempted""" override def toString = s"TooUnspecific" /** An ambiguous implicits failure */ @@ -1484,7 +1487,7 @@ trait Implicits: private def searchImplicit(contextual: Boolean): SearchResult = if isUnderspecified(wildProto) then - NoMatchingImplicitsFailure + SearchFailure(TooUnspecific(pt), span) else val eligible = if contextual then diff --git a/tests/neg/i15998.check b/tests/neg/i15998.check new file mode 100644 index 000000000000..6e0e402ad92b --- /dev/null +++ b/tests/neg/i15998.check @@ -0,0 +1,36 @@ +-- [E007] Type Mismatch Error: tests/neg/i15998.scala:11:23 ------------------------------------------------------------ +11 | RingSeq.isRotationOf("DAB") // error + | ^^^^^ + | Found: ("DAB" : String) + | Required: CC[A] + | + | where: A is a type variable + | CC is a type variable with constraint <: [B] =>> collection.SeqOps[B, CC, CC[B]] + | + | Note that implicit conversions were not tried because the result of an implicit conversion + | must be more specific than CC[A] + | + | longer explanation available when compiling with `-explain` +-- [E008] Not Found Error: tests/neg/i15998.scala:12:9 ----------------------------------------------------------------- +12 | "ABCD".isRotationOf("DAB") // error + | ^^^^^^^^^^^^^^^^^^^ + | value isRotationOf is not a member of String. + | An extension method was tried, but could not be fully constructed: + | + | RingSeq.isRotationOf[A, CC]("ABCD") failed with + | + | Found: ("ABCD" : String) + | Required: CC[A] + | + | where: A is a type variable + | CC is a type variable with constraint <: [B] =>> collection.SeqOps[B, CC, CC[B]] + | + | Note that implicit conversions were not tried because the result of an implicit conversion + | must be more specific than CC[A] +-- Error: tests/neg/i15998.scala:21:13 --------------------------------------------------------------------------------- +21 | val x = foo // error + | ^ + | No implicit search was attempted for parameter x of method foo + | since the expected type X is too unspecific + | + | where: X is a type variable diff --git a/tests/neg/i15998.scala b/tests/neg/i15998.scala new file mode 100644 index 000000000000..6535f2b9ef84 --- /dev/null +++ b/tests/neg/i15998.scala @@ -0,0 +1,21 @@ +import scala.collection.SeqOps + +trait ComparingOps: + extension[A, CC[B] <: SeqOps[B, CC, CC[B]]](ring: CC[A]) + def isRotationOf(that: CC[A]): Boolean = ??? + +object RingSeq extends ComparingOps +import RingSeq.* + +@main def Test = + RingSeq.isRotationOf("DAB") // error + "ABCD".isRotationOf("DAB") // error + + // workaround + RingSeq.isRotationOf[Char, IndexedSeq]("DAB") + RingSeq.isRotationOf(wrapString("DAB")) + wrapString("ABCD").isRotationOf("DAB") + + def foo[X](using x: X): X = x + + val x = foo // error From 8472f5b2309de62a68086b96e051d1744b3db636 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 8 Sep 2022 23:17:34 +0200 Subject: [PATCH 2/5] Better wildcard approximations of higher-kinded applications --- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 13 +++-- tests/neg/i15998.check | 54 +++++++------------ tests/neg/i15998.scala | 20 ++----- tests/run/i13986.scala | 15 ++++++ tests/{neg => run}/i13987.scala | 4 +- tests/run/i15998.scala | 17 ++++++ 7 files changed, 68 insertions(+), 57 deletions(-) create mode 100644 tests/run/i13986.scala rename tests/{neg => run}/i13987.scala (84%) create mode 100644 tests/run/i15998.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index dbb35c0ab761..cdd37a2f0be7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -264,7 +264,7 @@ class ImplicitSearchError( } case ambi @ TooUnspecific(target) => ex"""No implicit search was attempted${location("for")} - |since the expected type $target is too unspecific""" + |since the expected type $target is not specific enough""" case _ => val shortMessage = userDefinedImplicitNotFoundParamMessage .orElse(userDefinedImplicitNotFoundTypeMessage) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 71b500dc04a9..8dff12d82637 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -822,17 +822,22 @@ object ProtoTypes { /** Approximate occurrences of parameter types and uninstantiated typevars * by wildcard types. */ - private def wildApprox(tp: Type, theMap: WildApproxMap | Null, seen: Set[TypeParamRef], internal: Set[TypeLambda])(using Context): Type = tp match { + private def wildApprox(tp: Type, theMap: WildApproxMap | Null, seen: Set[TypeParamRef], internal: Set[TypeLambda])(using Context): Type = + tp match { case tp: NamedType => // default case, inlined for speed val isPatternBoundTypeRef = tp.isInstanceOf[TypeRef] && tp.symbol.isPatternBound if (isPatternBoundTypeRef) WildcardType(tp.underlying.bounds) else if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen, internal)) case tp @ AppliedType(tycon, args) => + def wildArgs = args.mapConserve(arg => wildApprox(arg, theMap, seen, internal)) wildApprox(tycon, theMap, seen, internal) match { - case _: WildcardType => WildcardType // this ensures we get a * type - case tycon1 => tp.derivedAppliedType(tycon1, - args.mapConserve(arg => wildApprox(arg, theMap, seen, internal))) + case WildcardType(TypeBounds(lo, hi)) if hi.typeParams.hasSameLengthAs(args) => + val lo1 = if lo.typeParams.hasSameLengthAs(args) then lo.appliedTo(wildArgs) else lo + WildcardType(TypeBounds(lo1, hi.appliedTo(wildArgs))) + case WildcardType(_) => + WildcardType + case tycon1 => tp.derivedAppliedType(tycon1, wildArgs) } case tp: RefinedType => // default case, inlined for speed tp.derivedRefinedType( diff --git a/tests/neg/i15998.check b/tests/neg/i15998.check index 6e0e402ad92b..c745c7a84309 100644 --- a/tests/neg/i15998.check +++ b/tests/neg/i15998.check @@ -1,36 +1,20 @@ --- [E007] Type Mismatch Error: tests/neg/i15998.scala:11:23 ------------------------------------------------------------ -11 | RingSeq.isRotationOf("DAB") // error - | ^^^^^ - | Found: ("DAB" : String) - | Required: CC[A] +-- [E007] Type Mismatch Error: tests/neg/i15998.scala:6:12 ------------------------------------------------------------- +6 |val _ = foo(1) // error + | ^ + | Found: (1 : Int) + | Required: CC[A] + | + | where: A is a type variable + | CC is a type variable with constraint <: [B] =>> Any + | + | Note that implicit conversions were not tried because the result of an implicit conversion + | must be more specific than CC[A] + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i15998.scala:11:11 --------------------------------------------------------------------------------- +11 |val _ = bar // error + | ^ + | No implicit search was attempted for parameter x of method bar + | since the expected type X is not specific enough | - | where: A is a type variable - | CC is a type variable with constraint <: [B] =>> collection.SeqOps[B, CC, CC[B]] - | - | Note that implicit conversions were not tried because the result of an implicit conversion - | must be more specific than CC[A] - | - | longer explanation available when compiling with `-explain` --- [E008] Not Found Error: tests/neg/i15998.scala:12:9 ----------------------------------------------------------------- -12 | "ABCD".isRotationOf("DAB") // error - | ^^^^^^^^^^^^^^^^^^^ - | value isRotationOf is not a member of String. - | An extension method was tried, but could not be fully constructed: - | - | RingSeq.isRotationOf[A, CC]("ABCD") failed with - | - | Found: ("ABCD" : String) - | Required: CC[A] - | - | where: A is a type variable - | CC is a type variable with constraint <: [B] =>> collection.SeqOps[B, CC, CC[B]] - | - | Note that implicit conversions were not tried because the result of an implicit conversion - | must be more specific than CC[A] --- Error: tests/neg/i15998.scala:21:13 --------------------------------------------------------------------------------- -21 | val x = foo // error - | ^ - | No implicit search was attempted for parameter x of method foo - | since the expected type X is too unspecific - | - | where: X is a type variable + | where: X is a type variable diff --git a/tests/neg/i15998.scala b/tests/neg/i15998.scala index 6535f2b9ef84..964e795fdfca 100644 --- a/tests/neg/i15998.scala +++ b/tests/neg/i15998.scala @@ -1,21 +1,11 @@ -import scala.collection.SeqOps -trait ComparingOps: - extension[A, CC[B] <: SeqOps[B, CC, CC[B]]](ring: CC[A]) - def isRotationOf(that: CC[A]): Boolean = ??? +given split: Conversion[Int, List[Int]] = ??? -object RingSeq extends ComparingOps -import RingSeq.* +def foo[A, CC[B]](ring: CC[A]): Unit = () -@main def Test = - RingSeq.isRotationOf("DAB") // error - "ABCD".isRotationOf("DAB") // error +val _ = foo(1) // error - // workaround - RingSeq.isRotationOf[Char, IndexedSeq]("DAB") - RingSeq.isRotationOf(wrapString("DAB")) - wrapString("ABCD").isRotationOf("DAB") - def foo[X](using x: X): X = x +def bar[X](using x: X): X = x - val x = foo // error +val _ = bar // error diff --git a/tests/run/i13986.scala b/tests/run/i13986.scala new file mode 100644 index 000000000000..e881a7e1486f --- /dev/null +++ b/tests/run/i13986.scala @@ -0,0 +1,15 @@ +package example + +sealed trait Xa[T] +sealed trait Mu[T] extends Xa[T] +object Xa { + implicit def convertMu[X[x] <: Xa[x], A, B](implicit t: X[A]): X[B] = t.asInstanceOf[X[B]] +} +object Mu { + implicit def mu: Mu[Int] = new Mu[Int] {} +} + +object Test extends App { + def constrain(a: Mu[Long]): Unit = () + constrain(Xa.convertMu) +} \ No newline at end of file diff --git a/tests/neg/i13987.scala b/tests/run/i13987.scala similarity index 84% rename from tests/neg/i13987.scala rename to tests/run/i13987.scala index b27cd444cda6..7ddaa69f0653 100644 --- a/tests/neg/i13987.scala +++ b/tests/run/i13987.scala @@ -10,7 +10,7 @@ object Mu { implicit def mu: Mu[Int] = new Mu[Int] {} } -object App extends App { - def constrain(a: Mu[Long]): Unit = println(a) +object Test extends App { + def constrain(a: Mu[Long]): Unit = () constrain(Xa.convertMu) // error } \ No newline at end of file diff --git a/tests/run/i15998.scala b/tests/run/i15998.scala new file mode 100644 index 000000000000..2e1ff697b433 --- /dev/null +++ b/tests/run/i15998.scala @@ -0,0 +1,17 @@ +import scala.collection.SeqOps + +trait ComparingOps: + extension[A, CC[B] <: SeqOps[B, CC, CC[B]]](ring: CC[A]) + def isRotationOf(that: CC[A]): Boolean = true + +object RingSeq extends ComparingOps +import RingSeq.* + +@main def Test = + RingSeq.isRotationOf("DAB") // error + "ABCD".isRotationOf("DAB") // error + + // workaround + RingSeq.isRotationOf[Char, IndexedSeq]("DAB") + RingSeq.isRotationOf(wrapString("DAB")) + wrapString("ABCD").isRotationOf("DAB") From 9c868c656c6b431b8d4ff49aa999b3ba806e9fbb Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 9 Sep 2022 09:04:17 +0200 Subject: [PATCH 3/5] Fix test --- tests/run/i13986.check | 1 + tests/run/i13986.scala | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 tests/run/i13986.check diff --git a/tests/run/i13986.check b/tests/run/i13986.check new file mode 100644 index 000000000000..92cfbd63ad69 --- /dev/null +++ b/tests/run/i13986.check @@ -0,0 +1 @@ +mu diff --git a/tests/run/i13986.scala b/tests/run/i13986.scala index e881a7e1486f..16f79622e92e 100644 --- a/tests/run/i13986.scala +++ b/tests/run/i13986.scala @@ -1,15 +1,15 @@ -package example - sealed trait Xa[T] sealed trait Mu[T] extends Xa[T] object Xa { implicit def convertMu[X[x] <: Xa[x], A, B](implicit t: X[A]): X[B] = t.asInstanceOf[X[B]] } object Mu { - implicit def mu: Mu[Int] = new Mu[Int] {} + implicit def mu: Mu[Int] = new Mu[Int] { + override def toString = "mu" + } } object Test extends App { - def constrain(a: Mu[Long]): Unit = () + def constrain(a: Mu[Long]): Unit = println(a) constrain(Xa.convertMu) } \ No newline at end of file From db09d12aa8755451a15fb0934defdd7b4177de8a Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 9 Sep 2022 10:52:16 +0200 Subject: [PATCH 4/5] More tests that work now --- tests/pos/i15160.scala | 17 +++++++++++++++++ tests/pos/i15670.scala | 29 +++++++++++++++++++++++++++++ tests/pos/i15820.scala | 8 ++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/pos/i15160.scala create mode 100644 tests/pos/i15670.scala create mode 100644 tests/pos/i15820.scala diff --git a/tests/pos/i15160.scala b/tests/pos/i15160.scala new file mode 100644 index 000000000000..cc55e0f5fb19 --- /dev/null +++ b/tests/pos/i15160.scala @@ -0,0 +1,17 @@ +trait Eq[A] { + def eqv(a1: A, a2: A): Boolean +} + +given stringEq: Eq[String] with { + def eqv(a1: String, a2: String) = a1 == a2 +} + +abstract class Newtype[Src] { + opaque type Type = Src + + protected final def derive[F[_]](using ev: F[Src]): F[Type] = ev +} + +object Sample extends Newtype[String] { + given eq: Eq[Type] = derive +} \ No newline at end of file diff --git a/tests/pos/i15670.scala b/tests/pos/i15670.scala new file mode 100644 index 000000000000..b46b3708fe4e --- /dev/null +++ b/tests/pos/i15670.scala @@ -0,0 +1,29 @@ +trait JsonRowEntry { + def readAs[E](implicit c: Read[E]): Option[E] = ??? +} +trait Read[T] +trait Codec[T] extends Read[T] +trait CodecTypeProjection[C[_]] +object JsonTransform { + given SetCodec[T, C[_]: CodecTypeProjection]: scala.Conversion[C[T], C[Set[T]]] = ??? + given SetCodecExp[T, C[_]: CodecTypeProjection](using codec: C[T]): C[Set[T]] = codec + given Codec[String] = ??? + given CodecTypeProjection[Read] = ??? +} + +@main def Test() = { + import JsonTransform.given + val tree = new JsonRowEntry {} + tree.readAs[Set[String]] +} + +trait Box[E] + +trait Domain + +def fun[E, D[_] <: Domain](box: Box[E])(implicit domain: D[E]): Unit = { + + val newBox: Box[E] = ??? + + fun(newBox) +} diff --git a/tests/pos/i15820.scala b/tests/pos/i15820.scala new file mode 100644 index 000000000000..4425760ffe3c --- /dev/null +++ b/tests/pos/i15820.scala @@ -0,0 +1,8 @@ +sealed trait Domain[E] + +final def splitBounds[E, D[X] <: Domain[X]]( + bounds: Seq[E], + )( using domain: D[E]): Seq[E] = + val newBounds: Seq[E] = ??? + splitBounds(newBounds) // does not compile + splitBounds[E,D](newBounds) // does compile \ No newline at end of file From a451baa0936d8df991483a4e55affc905a41f4f8 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 9 Sep 2022 14:13:01 +0200 Subject: [PATCH 5/5] Update compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala Co-authored-by: Guillaume Martres --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 8dff12d82637..b53ef28dc8f7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -833,8 +833,9 @@ object ProtoTypes { def wildArgs = args.mapConserve(arg => wildApprox(arg, theMap, seen, internal)) wildApprox(tycon, theMap, seen, internal) match { case WildcardType(TypeBounds(lo, hi)) if hi.typeParams.hasSameLengthAs(args) => - val lo1 = if lo.typeParams.hasSameLengthAs(args) then lo.appliedTo(wildArgs) else lo - WildcardType(TypeBounds(lo1, hi.appliedTo(wildArgs))) + val args1 = wildArgs + val lo1 = if lo.typeParams.hasSameLengthAs(args) then lo.appliedTo(args1) else lo + WildcardType(TypeBounds(lo1, hi.appliedTo(args1))) case WildcardType(_) => WildcardType case tycon1 => tp.derivedAppliedType(tycon1, wildArgs)