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 all 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
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,6 @@ object Mode {
/** Read comments from definitions when unpickling from TASTY */
val ReadComments: Mode = newMode(22, "ReadComments")

/** Suppress insertion of apply or implicit conversion on qualifier */
val FixedQualifier: Mode = newMode(23, "FixedQualifier")
/** We are synthesizing the receiver of an extension method */
val SynthesizeExtMethodReceiver: Mode = newMode(23, "SynthesizeExtMethodReceiver")
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
/** Defer constraining type variables when compared against prototypes */
def isMatchedByProto(proto: ProtoType, tp: Type): Boolean = tp.stripTypeVar match {
case tp: TypeParamRef if constraint contains tp => true
case _ => proto.isMatchedBy(tp)
case _ => proto.isMatchedBy(tp, keepConstraint = true)
}

/** Narrow gadt.bounds for the type parameter referenced by `tr` to include
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1665,7 +1665,7 @@ object Types {

/** A trait for proto-types, used as expected types in typer */
trait ProtoType extends Type {
def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean
def isMatchedBy(tp: Type, keepConstraint: Boolean = false)(implicit ctx: Context): Boolean
def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T
def map(tm: TypeMap)(implicit ctx: Context): ProtoType

Expand Down
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
28 changes: 15 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
private[this] var _ok = true

def ok: Boolean = _ok
def ok_=(x: Boolean): Unit = {
assert(x || ctx.reporter.errorsReported || !ctx.typerState.isCommittable) // !!! DEBUG
_ok = x
}
def ok_=(x: Boolean): Unit = _ok = x

/** The function's type after widening and instantiating polytypes
* with TypeParamRefs in constraint set
Expand Down Expand Up @@ -802,7 +799,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
* part. Return an optional value to indicate success.
*/
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] =
if (ctx.mode.is(Mode.FixedQualifier)) None
if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver))
// Suppress insertion of apply or implicit conversion on extension method receiver
None
else
tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 =>
tryEither {
Expand Down Expand Up @@ -1118,8 +1117,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
/** Is given method reference applicable to type arguments `targs` and argument trees `args`?
* @param resultType The expected result type of the application
*/
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
ctx.test(implicit ctx => new ApplicableToTrees(methRef, targs, args, resultType).success)
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
def isApp(implicit ctx: Context): Boolean =
new ApplicableToTrees(methRef, targs, args, resultType).success
if (keepConstraint) isApp else ctx.test(implicit ctx => isApp)
}

/** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views?
* @param resultType The expected result type of the application
Expand All @@ -1137,8 +1139,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
* possibly after inserting an `apply`?
* @param resultType The expected result type of the application
*/
def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
onMethod(tp, isApplicable(_, targs, args, resultType))
def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
onMethod(tp, isApplicable(_, targs, args, resultType, keepConstraint))

/** Is given type applicable to argument types `args`, possibly after inserting an `apply`?
* @param resultType The expected result type of the application
Expand Down Expand Up @@ -1491,7 +1493,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
)
if (alts2.isEmpty && !ctx.isAfterTyper)
alts.filter(alt =>
isApplicable(alt, targs, args, resultType)
isApplicable(alt, targs, args, resultType, keepConstraint = false)
)
else
alts2
Expand All @@ -1511,14 +1513,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
}

case pt @ PolyProto(targs1, pt1) if targs.isEmpty =>
val alts1 = alts filter pt.isMatchedBy
val alts1 = alts.filter(pt.isMatchedBy(_))
resolveOverloaded(alts1, pt1, targs1.tpes)

case defn.FunctionOf(args, resultType, _, _) =>
narrowByTypes(alts, args, resultType)

case pt =>
val compat = alts.filter(normalizedCompatible(_, pt))
val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false))
if (compat.isEmpty)
/*
* the case should not be moved to the enclosing match
Expand Down Expand Up @@ -1691,7 +1693,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
}
val app =
typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)(
ctx.addMode(Mode.FixedQualifier))
ctx.addMode(Mode.SynthesizeExtMethodReceiver))
if (!app.symbol.is(Extension))
ctx.error(em"not an extension method: $methodRef", receiver.sourcePos)
app
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
}
53 changes: 38 additions & 15 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,28 @@ object ProtoTypes {
def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
(tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt)

/** Test compatibility after normalization in a fresh typerstate. */
def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
ctx.test { implicit ctx =>
/** Test compatibility after normalization.
* Do this in a fresh typerstate unless `keepConstraint` is true.
*/
def normalizedCompatible(tp: Type, pt: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
def testCompat(implicit ctx: Context): Boolean = {
val normTp = normalize(tp, pt)
isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless
}
if (keepConstraint)
tp.widenSingleton match {
case poly: PolyType =>
// We can't keep the constraint in this case, since we have to add type parameters
// to it, but there's no place to associate them with type variables.
// So we'd get a "inconsistent: no typevars were added to committable constraint"
// assertion failure in `constrained`. To do better, we'd have to change the
// constraint handling architecture so that some type parameters are committable
// and others are not. But that's a whole different ballgame.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a pending test that demonstrates the current limitation here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a test for this (but agree it would be good to have one).

normalizedCompatible(tp, pt, keepConstraint = false)
case _ => testCompat
}
else ctx.test(implicit ctx => testCompat)
}

private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match {
case _: OrType => true
Expand Down Expand Up @@ -89,7 +105,7 @@ object ProtoTypes {

/** A trait for prototypes that match all types */
trait MatchAlways extends ProtoType {
def isMatchedBy(tp1: Type)(implicit ctx: Context): Boolean = true
def isMatchedBy(tp1: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = true
def map(tm: TypeMap)(implicit ctx: Context): ProtoType = this
def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = x
override def toString: String = getClass.toString
Expand Down Expand Up @@ -131,13 +147,13 @@ object ProtoTypes {
case _ => false
}

override def isMatchedBy(tp1: Type)(implicit ctx: Context): Boolean = {
override def isMatchedBy(tp1: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
name == nme.WILDCARD || hasUnknownMembers(tp1) ||
{
val mbr = if (privateOK) tp1.member(name) else tp1.nonPrivateMember(name)
def qualifies(m: SingleDenotation) =
memberProto.isRef(defn.UnitClass) ||
tp1.isValueType && compat.normalizedCompatible(NamedType(tp1, name, m), memberProto)
tp1.isValueType && compat.normalizedCompatible(NamedType(tp1, name, m), memberProto, keepConstraint)
// Note: can't use `m.info` here because if `m` is a method, `m.info`
// loses knowledge about `m`'s default arguments.
mbr match { // hasAltWith inlined for performance
Expand Down Expand Up @@ -234,8 +250,13 @@ object ProtoTypes {
extends UncachedGroundType with ApplyingProto with FunOrPolyProto {
override def resultType(implicit ctx: Context): Type = resType

def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean =
typer.isApplicable(tp, Nil, unforcedTypedArgs, resultType)
def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
val args = unforcedTypedArgs
def isPoly(tree: Tree) = tree.tpe.widenSingleton.isInstanceOf[PolyType]
// See remark in normalizedCompatible for why we can't keep the constraint
// if one of the arguments has a PolyType.
typer.isApplicable(tp, Nil, args, resultType, keepConstraint && !args.exists(isPoly))
}

def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
Expand Down Expand Up @@ -292,11 +313,13 @@ object ProtoTypes {
* with unknown parameter types - this will then cause a
* "missing parameter type" error
*/
private def typedArgs(force: Boolean): List[Tree] = {
if (state.typedArgs.size != args.length)
state.typedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
state.typedArgs
}
private def typedArgs(force: Boolean): List[Tree] =
if (state.typedArgs.size == args.length) state.typedArgs
else {
val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
if (force || !args1.contains(WildcardType)) state.typedArgs = args1
args1
}

def typedArgs: List[Tree] = typedArgs(force = true)
def unforcedTypedArgs: List[Tree] = typedArgs(force = false)
Expand Down Expand Up @@ -379,7 +402,7 @@ object ProtoTypes {

override def resultType(implicit ctx: Context): Type = resType

def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean =
def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
ctx.typer.isApplicable(tp, argType :: Nil, resultType) || {
resType match {
case SelectionProto(name: TermName, mbrType, _, _) =>
Expand Down Expand Up @@ -422,7 +445,7 @@ object ProtoTypes {

override def resultType(implicit ctx: Context): Type = resType

override def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = {
override def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
def isInstantiatable(tp: Type) = tp.widen match {
case tp: PolyType => tp.paramNames.length == targs.length
case _ => false
Expand Down
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
Loading