Skip to content

Fix #5773: Apply more context info to avoid ambiguous implicits #5836

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

Merged
merged 13 commits into from
Feb 7, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
case result: AmbiguousImplicits =>
"Ambiguous Implicit: " ~ toText(result.alt1.ref) ~ " and " ~ toText(result.alt2.ref)
case _ =>
"?Unknown Implicit Result?" + result.getClass
"Search Failure: " ~ toText(result.tree)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ object messages {
val explanation: String = ""
}

case class NotAMember(site: Type, name: Name, selected: String)(implicit ctx: Context)
case class NotAMember(site: Type, name: Name, selected: String, addendum: String = "")(implicit ctx: Context)
extends Message(NotAMemberID) {
val kind: String = "Member Not Found"

Expand Down Expand Up @@ -360,7 +360,7 @@ object messages {
)
}

ex"$selected $name is not a member of ${site.widen}$closeMember"
ex"$selected $name is not a member of ${site.widen}$closeMember$addendum"
}

val explanation: String = ""
Expand Down
17 changes: 12 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ object Implicits {
def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = {

def methodCandidateKind(mt: MethodType, approx: Boolean) =
if (!mt.isImplicitMethod &&
mt.paramInfos.lengthCompare(1) == 0 && {
if (mt.isImplicitMethod)
viewCandidateKind(normalize(mt, pt), argType, resType)
else if (mt.paramInfos.lengthCompare(1) == 0 && {
var formal = widenSingleton(mt.paramInfos.head)
if (approx) formal = wildApprox(formal)
ctx.test(implicit ctx => argType relaxed_<:< formal)
Expand Down Expand Up @@ -1274,9 +1275,13 @@ trait Implicits { self: Typer =>
case reason =>
if (contextual)
bestImplicit(contextual = false).recoverWith {
failure2 => reason match {
case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure
case _ => failure2
failure2 => failure2.reason match {
case _: AmbiguousImplicits => failure2
case _ =>
reason match {
case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure
case _ => List(failure, failure2).maxBy(_.tree.treeSize)
}
}
}
else failure
Expand Down Expand Up @@ -1635,4 +1640,6 @@ final class TermRefSet(implicit ctx: Context) {
foreach(tr => buffer += tr)
buffer.toList
}

override def toString = toList.toString
}
54 changes: 31 additions & 23 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -234,38 +234,46 @@ trait TypeAssigner {
test(tpe, true)
}

/** The type of a selection with `name` of a tree with type `site`.
*/
def selectionType(site: Type, name: Name, pos: SourcePosition)(implicit ctx: Context): Type = {
val mbr = site.member(name)
/** The type of the selection `tree`, where `qual1` is the typed qualifier part. */
def selectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
var qualType = qual1.tpe.widenIfUnstable
if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR)
// constructors are selected on typeconstructor, type arguments are passed afterwards
qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos)
else if (!qualType.isInstanceOf[TermType])
qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos)
val name = tree.name
val mbr = qualType.member(name)
if (reallyExists(mbr))
site.select(name, mbr)
else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name))
qualType.select(name, mbr)
else if (qualType.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name))
TryDynamicCallType
else if (site.isErroneous || name.toTermName == nme.ERROR)
else if (qualType.isErroneous || name.toTermName == nme.ERROR)
UnspecifiedErrorType
else if (name == nme.CONSTRUCTOR)
errorType(ex"$qualType does not have a constructor", tree.sourcePos)
else {
def kind = if (name.isTypeName) "type" else "value"
def addendum =
if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?"
else ""
errorType(
if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor"
else NotAMember(site, name, kind),
pos)
val kind = if (name.isTypeName) "type" else "value"
val addendum =
if (qualType.derivesFrom(defn.DynamicClass))
"\npossible cause: maybe a wrong Dynamic method signature?"
else qual1.getAttachment(Typer.HiddenSearchFailure) match {
case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] =>
i""".
|An extension method was tried, but could not be fully constructed:
|
| ${failure.tree.show.replace("\n", "\n ")}"""
case _ => ""
}
errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos)
}
}

/** The selection type, which is additionally checked for accessibility.
/** The type of the selection in `tree`, where `qual1` is the typed qualifier part.
* The selection type is additionally checked for accessibility.
*/
def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
var qualType = qual1.tpe.widenIfUnstable
if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR)
// constructors are selected on typeconstructor, type arguments are passed afterwards
qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos)
else if (!qualType.isInstanceOf[TermType])
qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos)
val ownType = selectionType(qualType, tree.name, tree.sourcePos)
val ownType = selectionType(tree, qual1)
if (tree.getAttachment(desugar.SuppressAccessCheck).isDefined) ownType
else ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.sourcePos)
}
Expand Down
13 changes: 10 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import transform.SymUtils._
import transform.TypeUtils._
import reporting.trace


object Typer {

/** The precedence of bindings which determines which of several bindings will be
Expand Down Expand Up @@ -75,6 +74,11 @@ object Typer {
* the `()` was dropped by the Typer.
*/
private val DroppedEmptyArgs = new Property.Key[Unit]

/** Am attachment that indicates a failed conversion or extension method
* search was tried on a tree. This will in some cases be reported in error messages
*/
private[typer] val HiddenSearchFailure = new Property.Key[SearchFailure]
}

class Typer extends Namer
Expand Down Expand Up @@ -2724,11 +2728,14 @@ class Typer extends Namer
checkImplicitConversionUseOK(inferred.symbol, tree.posd)
readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled))
case failure: SearchFailure =>
if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous)
if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) {
// don't report the failure but return the tree unchanged. This
// will cause a failure at the next level out, which usually gives
// a better error message.
// a better error message. To compensate, store the encountered failure
// as an attachment, so that it can be reported later as an addendum.
tree.putAttachment(HiddenSearchFailure, failure)
tree
}
else recover(failure.reason)
}
else recover(NoMatchingImplicits)
Expand Down
32 changes: 32 additions & 0 deletions tests/neg/i5773.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
trait Semigroup[T] {
def (lhs: T) append (rhs: T): T
}

object Semigroup {
implicit object stringAppend extends Semigroup[String] {
override def (lhs: String) append (rhs: String): String = lhs + rhs
}

implicit def sumSemigroup[N](implicit N: Numeric[N]): Semigroup[N] = new {
override def (lhs: N) append (rhs: N): N = ??? // N.plus(lhs, rhs)
}

implicit class SumSemigroupDeco[N](implicit N: Numeric[N]) extends Semigroup[N] {
override def (lhs: N) append (rhs: N): N = ??? // N.plus(lhs, rhs)
}
}


object Main {
import Semigroup.sumSemigroup // this is not sufficient
def f1 = {
import Semigroup.stringAppend // necessary to make the extension method visible
println("Hi" append " mum") // ok
println(1 append 2) // error: this won't compile
}

def f2 = {
implicit val intSumAppend: Semigroup[Int] = sumSemigroup[Int]
println(3 append 4) // this will
}
}