diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 3c92a217206d..cdd37a2f0be7 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 not specific enough""" 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/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 71b500dc04a9..b53ef28dc8f7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -822,17 +822,23 @@ 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 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) } case tp: RefinedType => // default case, inlined for speed tp.derivedRefinedType( diff --git a/tests/neg/i15998.check b/tests/neg/i15998.check new file mode 100644 index 000000000000..c745c7a84309 --- /dev/null +++ b/tests/neg/i15998.check @@ -0,0 +1,20 @@ +-- [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: X is a type variable diff --git a/tests/neg/i15998.scala b/tests/neg/i15998.scala new file mode 100644 index 000000000000..964e795fdfca --- /dev/null +++ b/tests/neg/i15998.scala @@ -0,0 +1,11 @@ + +given split: Conversion[Int, List[Int]] = ??? + +def foo[A, CC[B]](ring: CC[A]): Unit = () + +val _ = foo(1) // error + + +def bar[X](using x: X): X = x + +val _ = bar // error 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 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 new file mode 100644 index 000000000000..16f79622e92e --- /dev/null +++ b/tests/run/i13986.scala @@ -0,0 +1,15 @@ +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] { + override def toString = "mu" + } +} + +object Test extends App { + def constrain(a: Mu[Long]): Unit = println(a) + 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")