Skip to content

Commit 8726cf7

Browse files
authored
Merge pull request #5836 from dotty-staging/fix-#5773
Fix #5773: Apply more context info to avoid ambiguous implicits
2 parents d70d69b + 75178c4 commit 8726cf7

File tree

13 files changed

+222
-75
lines changed

13 files changed

+222
-75
lines changed

compiler/src/dotty/tools/dotc/core/Mode.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,6 @@ object Mode {
104104
/** Read comments from definitions when unpickling from TASTY */
105105
val ReadComments: Mode = newMode(22, "ReadComments")
106106

107-
/** Suppress insertion of apply or implicit conversion on qualifier */
108-
val FixedQualifier: Mode = newMode(23, "FixedQualifier")
107+
/** We are synthesizing the receiver of an extension method */
108+
val SynthesizeExtMethodReceiver: Mode = newMode(23, "SynthesizeExtMethodReceiver")
109109
}

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1227,7 +1227,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
12271227
/** Defer constraining type variables when compared against prototypes */
12281228
def isMatchedByProto(proto: ProtoType, tp: Type): Boolean = tp.stripTypeVar match {
12291229
case tp: TypeParamRef if constraint contains tp => true
1230-
case _ => proto.isMatchedBy(tp)
1230+
case _ => proto.isMatchedBy(tp, keepConstraint = true)
12311231
}
12321232

12331233
/** Narrow gadt.bounds for the type parameter referenced by `tr` to include

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,7 @@ object Types {
16651665

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

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
524524
case result: AmbiguousImplicits =>
525525
"Ambiguous Implicit: " ~ toText(result.alt1.ref) ~ " and " ~ toText(result.alt2.ref)
526526
case _ =>
527-
"?Unknown Implicit Result?" + result.getClass
527+
"Search Failure: " ~ toText(result.tree)
528528
}
529529
}
530530

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ object messages {
301301
val explanation: String = ""
302302
}
303303

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

@@ -361,7 +361,7 @@ object messages {
361361
)
362362
}
363363

364-
ex"$selected $name is not a member of ${site.widen}$closeMember"
364+
ex"$selected $name is not a member of ${site.widen}$closeMember$addendum"
365365
}
366366

367367
val explanation: String = ""

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
273273
private[this] var _ok = true
274274

275275
def ok: Boolean = _ok
276-
def ok_=(x: Boolean): Unit = {
277-
assert(x || ctx.reporter.errorsReported || !ctx.typerState.isCommittable) // !!! DEBUG
278-
_ok = x
279-
}
276+
def ok_=(x: Boolean): Unit = _ok = x
280277

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

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

11401142
/** Is given type applicable to argument types `args`, possibly after inserting an `apply`?
11411143
* @param resultType The expected result type of the application
@@ -1488,7 +1490,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14881490
)
14891491
if (alts2.isEmpty && !ctx.isAfterTyper)
14901492
alts.filter(alt =>
1491-
isApplicable(alt, targs, args, resultType)
1493+
isApplicable(alt, targs, args, resultType, keepConstraint = false)
14921494
)
14931495
else
14941496
alts2
@@ -1508,14 +1510,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15081510
}
15091511

15101512
case pt @ PolyProto(targs1, pt1) if targs.isEmpty =>
1511-
val alts1 = alts filter pt.isMatchedBy
1513+
val alts1 = alts.filter(pt.isMatchedBy(_))
15121514
resolveOverloaded(alts1, pt1, targs1.tpes)
15131515

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

15171519
case pt =>
1518-
val compat = alts.filter(normalizedCompatible(_, pt))
1520+
val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false))
15191521
if (compat.isEmpty)
15201522
/*
15211523
* the case should not be moved to the enclosing match
@@ -1688,7 +1690,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
16881690
}
16891691
val app =
16901692
typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)(
1691-
ctx.addMode(Mode.FixedQualifier))
1693+
ctx.addMode(Mode.SynthesizeExtMethodReceiver))
16921694
if (!app.symbol.is(Extension))
16931695
ctx.error(em"not an extension method: $methodRef", receiver.sourcePos)
16941696
app

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ object Implicits {
9393
def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = {
9494

9595
def methodCandidateKind(mt: MethodType, approx: Boolean) =
96-
if (!mt.isImplicitMethod &&
97-
mt.paramInfos.lengthCompare(1) == 0 && {
96+
if (mt.isImplicitMethod)
97+
viewCandidateKind(normalize(mt, pt), argType, resType)
98+
else if (mt.paramInfos.lengthCompare(1) == 0 && {
9899
var formal = widenSingleton(mt.paramInfos.head)
99100
if (approx) formal = wildApprox(formal)
100101
ctx.test(implicit ctx => argType relaxed_<:< formal)
@@ -1274,9 +1275,13 @@ trait Implicits { self: Typer =>
12741275
case reason =>
12751276
if (contextual)
12761277
bestImplicit(contextual = false).recoverWith {
1277-
failure2 => reason match {
1278-
case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure
1279-
case _ => failure2
1278+
failure2 => failure2.reason match {
1279+
case _: AmbiguousImplicits => failure2
1280+
case _ =>
1281+
reason match {
1282+
case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure
1283+
case _ => List(failure, failure2).maxBy(_.tree.treeSize)
1284+
}
12801285
}
12811286
}
12821287
else failure
@@ -1635,4 +1640,6 @@ final class TermRefSet(implicit ctx: Context) {
16351640
foreach(tr => buffer += tr)
16361641
buffer.toList
16371642
}
1643+
1644+
override def toString = toList.toString
16381645
}

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,28 @@ object ProtoTypes {
3535
def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
3636
(tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt)
3737

38-
/** Test compatibility after normalization in a fresh typerstate. */
39-
def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
40-
ctx.test { implicit ctx =>
38+
/** Test compatibility after normalization.
39+
* Do this in a fresh typerstate unless `keepConstraint` is true.
40+
*/
41+
def normalizedCompatible(tp: Type, pt: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
42+
def testCompat(implicit ctx: Context): Boolean = {
4143
val normTp = normalize(tp, pt)
4244
isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless
4345
}
46+
if (keepConstraint)
47+
tp.widenSingleton match {
48+
case poly: PolyType =>
49+
// We can't keep the constraint in this case, since we have to add type parameters
50+
// to it, but there's no place to associate them with type variables.
51+
// So we'd get a "inconsistent: no typevars were added to committable constraint"
52+
// assertion failure in `constrained`. To do better, we'd have to change the
53+
// constraint handling architecture so that some type parameters are committable
54+
// and others are not. But that's a whole different ballgame.
55+
normalizedCompatible(tp, pt, keepConstraint = false)
56+
case _ => testCompat
57+
}
58+
else ctx.test(implicit ctx => testCompat)
59+
}
4460

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

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

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

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

240261
def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
241262
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
@@ -292,11 +313,13 @@ object ProtoTypes {
292313
* with unknown parameter types - this will then cause a
293314
* "missing parameter type" error
294315
*/
295-
private def typedArgs(force: Boolean): List[Tree] = {
296-
if (state.typedArgs.size != args.length)
297-
state.typedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
298-
state.typedArgs
299-
}
316+
private def typedArgs(force: Boolean): List[Tree] =
317+
if (state.typedArgs.size == args.length) state.typedArgs
318+
else {
319+
val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
320+
if (force || !args1.contains(WildcardType)) state.typedArgs = args1
321+
args1
322+
}
300323

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

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

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

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

425-
override def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = {
448+
override def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
426449
def isInstantiatable(tp: Type) = tp.widen match {
427450
case tp: PolyType => tp.paramNames.length == targs.length
428451
case _ => false

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -224,38 +224,46 @@ trait TypeAssigner {
224224
test(tpe, true)
225225
}
226226

227-
/** The type of a selection with `name` of a tree with type `site`.
228-
*/
229-
def selectionType(site: Type, name: Name, pos: SourcePosition)(implicit ctx: Context): Type = {
230-
val mbr = site.member(name)
227+
/** The type of the selection `tree`, where `qual1` is the typed qualifier part. */
228+
def selectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
229+
var qualType = qual1.tpe.widenIfUnstable
230+
if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR)
231+
// constructors are selected on typeconstructor, type arguments are passed afterwards
232+
qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos)
233+
else if (!qualType.isInstanceOf[TermType])
234+
qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos)
235+
val name = tree.name
236+
val mbr = qualType.member(name)
231237
if (reallyExists(mbr))
232-
site.select(name, mbr)
233-
else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name))
238+
qualType.select(name, mbr)
239+
else if (qualType.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name))
234240
TryDynamicCallType
235-
else if (site.isErroneous || name.toTermName == nme.ERROR)
241+
else if (qualType.isErroneous || name.toTermName == nme.ERROR)
236242
UnspecifiedErrorType
243+
else if (name == nme.CONSTRUCTOR)
244+
errorType(ex"$qualType does not have a constructor", tree.sourcePos)
237245
else {
238-
def kind = if (name.isTypeName) "type" else "value"
239-
def addendum =
240-
if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?"
241-
else ""
242-
errorType(
243-
if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor"
244-
else NotAMember(site, name, kind),
245-
pos)
246+
val kind = if (name.isTypeName) "type" else "value"
247+
val addendum =
248+
if (qualType.derivesFrom(defn.DynamicClass))
249+
"\npossible cause: maybe a wrong Dynamic method signature?"
250+
else qual1.getAttachment(Typer.HiddenSearchFailure) match {
251+
case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] =>
252+
i""".
253+
|An extension method was tried, but could not be fully constructed:
254+
|
255+
| ${failure.tree.show.replace("\n", "\n ")}"""
256+
case _ => ""
257+
}
258+
errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos)
246259
}
247260
}
248261

249-
/** The selection type, which is additionally checked for accessibility.
262+
/** The type of the selection in `tree`, where `qual1` is the typed qualifier part.
263+
* The selection type is additionally checked for accessibility.
250264
*/
251265
def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
252-
var qualType = qual1.tpe.widenIfUnstable
253-
if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR)
254-
// constructors are selected on typeconstructor, type arguments are passed afterwards
255-
qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos)
256-
else if (!qualType.isInstanceOf[TermType])
257-
qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos)
258-
val ownType = selectionType(qualType, tree.name, tree.sourcePos)
266+
val ownType = selectionType(tree, qual1)
259267
if (tree.getAttachment(desugar.SuppressAccessCheck).isDefined) ownType
260268
else ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.sourcePos)
261269
}

0 commit comments

Comments
 (0)