Skip to content
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

Improvements for implicit searches with top-level type variables #16001

Merged
merged 5 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

Expand Down Expand Up @@ -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)
Expand Down
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */")
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions tests/neg/i15998.check
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions tests/neg/i15998.scala
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions tests/pos/i15160.scala
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions tests/pos/i15670.scala
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 8 additions & 0 deletions tests/pos/i15820.scala
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/run/i13986.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mu
15 changes: 15 additions & 0 deletions tests/run/i13986.scala
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 2 additions & 2 deletions tests/neg/i13987.scala → tests/run/i13987.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
17 changes: 17 additions & 0 deletions tests/run/i15998.scala
Original file line number Diff line number Diff line change
@@ -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")