Skip to content

Commit 745fae6

Browse files
authored
Merge pull request #3421 from dotty-staging/fix-strawan
Rework implicit search
2 parents 7759e00 + db1b09a commit 745fae6

23 files changed

+741
-330
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ object Trees {
6969
}
7070

7171
/** A unique identifier for this tree. Used for debugging, and potentially
72-
* tracking presentation compiler interactions
72+
* tracking presentation compiler interactions.
7373
*/
74-
private var myUniqueId: Int = nxId
74+
@sharable private var myUniqueId: Int = nxId
7575

7676
def uniqueId = myUniqueId
7777

@@ -370,6 +370,11 @@ object Trees {
370370
override def toString = s"BackquotedIdent($name)"
371371
}
372372

373+
class SearchFailureIdent[-T >: Untyped] private[ast] (name: Name)
374+
extends Ident[T](name) {
375+
override def toString = s"SearchFailureIdent($name)"
376+
}
377+
373378
/** qualifier.name, or qualifier#name, if qualifier is a type */
374379
case class Select[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name)
375380
extends RefTree[T] {
@@ -830,6 +835,7 @@ object Trees {
830835

831836
type Ident = Trees.Ident[T]
832837
type BackquotedIdent = Trees.BackquotedIdent[T]
838+
type SearchFailureIdent = Trees.SearchFailureIdent[T]
833839
type Select = Trees.Select[T]
834840
type SelectWithSig = Trees.SelectWithSig[T]
835841
type This = Trees.This[T]

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
247247

248248
def Ident(name: Name): Ident = new Ident(name)
249249
def BackquotedIdent(name: Name): BackquotedIdent = new BackquotedIdent(name)
250+
def SearchFailureIdent(name: Name): SearchFailureIdent = new SearchFailureIdent(name)
250251
def Select(qualifier: Tree, name: Name): Select = new Select(qualifier, name)
251252
def SelectWithSig(qualifier: Tree, name: Name, sig: Signature): Select = new SelectWithSig(qualifier, name, sig)
252253
def This(qual: Ident): This = new This(qual)

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ class ScalaSettings extends Settings.SettingGroup {
2626
val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.")
2727
val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding)
2828
val explainTypes = BooleanSetting("-explain-types", "Explain type errors in more detail.")
29-
val explainImplicits = BooleanSetting("-explain-implicits", "Explain implicit search errors in more detail.")
3029
val explain = BooleanSetting("-explain", "Explain errors in more detail.")
3130
val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.")
3231
val help = BooleanSetting("-help", "Print a synopsis of standard options")

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,12 @@ class Definitions {
584584

585585
def Eq_eqAny(implicit ctx: Context) = EqModule.requiredMethod(nme.eqAny)
586586

587+
lazy val NotType = ctx.requiredClassRef("scala.implicits.Not")
588+
def NotClass(implicit ctx: Context) = NotType.symbol.asClass
589+
def NotModule(implicit ctx: Context) = NotClass.companionModule
590+
591+
def Not_value(implicit ctx: Context) = NotModule.requiredMethod(nme.value)
592+
587593
lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")
588594

589595
// Annotation base classes

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
8989
private[this] var testReporter: StoreReporter = null
9090

9191
/** Test using `op`, restoring typerState to previous state afterwards */
92-
def test(op: => Boolean): Boolean = {
92+
def test[T](op: => T): T = {
9393
val savedReporter = myReporter
9494
val savedConstraint = myConstraint
9595
val savedCommittable = myIsCommittable

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,7 +1978,7 @@ object Types {
19781978
else candidate
19791979

19801980
def withPrefix(prefix: Type)(implicit ctx: Context): NamedType = designator match {
1981-
case designator: TermSymbol =>
1981+
case designator: TermSymbol @unchecked =>
19821982
TermRef(prefix, designator)
19831983
case _ =>
19841984
// If symbol exists, the new signature is the symbol's signature as seen
@@ -3638,22 +3638,25 @@ object Types {
36383638
*/
36393639
abstract class FlexType extends UncachedGroundType with ValueType
36403640

3641-
class ErrorType private[Types] () extends FlexType {
3642-
def msg(implicit ctx: Context): Message =
3643-
ctx.errorTypeMsg.get(this) match {
3644-
case Some(msgFun) => msgFun()
3645-
case None => "error message from previous run no longer available"
3646-
}
3641+
abstract class ErrorType extends FlexType {
3642+
def msg(implicit ctx: Context): Message
36473643
}
3644+
36483645
object ErrorType {
36493646
def apply(msg: => Message)(implicit ctx: Context): ErrorType = {
3650-
val et = new ErrorType
3647+
val et = new ErrorType {
3648+
def msg(implicit ctx: Context): Message =
3649+
ctx.errorTypeMsg.get(this) match {
3650+
case Some(msgFun) => msgFun()
3651+
case None => "error message from previous run no longer available"
3652+
}
3653+
}
36513654
ctx.base.errorTypeMsg(et) = () => msg
36523655
et
36533656
}
36543657
}
36553658

3656-
object UnspecifiedErrorType extends ErrorType() {
3659+
object UnspecifiedErrorType extends ErrorType {
36573660
override def msg(implicit ctx: Context): Message = "unspecified error"
36583661
}
36593662

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

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
171171
changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) }
172172
case OrType(tp1, tp2) =>
173173
changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) }
174-
case _: ErrorType =>
175-
"<error>"
174+
case tp: ErrorType =>
175+
s"<error ${tp.msg.msg}>"
176176
case tp: WildcardType =>
177177
if (tp.optBounds.exists) "(?" ~ toTextRHS(tp.bounds) ~ ")" else "?"
178178
case NoType =>
@@ -202,8 +202,6 @@ class PlainPrinter(_ctx: Context) extends Printer {
202202
ParamRefNameString(tp) ~ ".type"
203203
case AnnotatedType(tpe, annot) =>
204204
toTextLocal(tpe) ~ " " ~ toText(annot)
205-
case AppliedType(tycon, args) =>
206-
toTextLocal(tycon) ~ "[" ~ Text(args.map(argText), ", ") ~ "]"
207205
case tp: TypeVar =>
208206
if (tp.isInstantiated)
209207
toTextLocal(tp.instanceOpt) ~ ("^" provided ctx.settings.YprintDebug.value)
@@ -501,18 +499,16 @@ class PlainPrinter(_ctx: Context) extends Printer {
501499
def toText(result: SearchResult): Text = result match {
502500
case result: SearchSuccess =>
503501
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)
504-
case _: NonMatchingImplicit | NoImplicitMatches =>
505-
"NoImplicitMatches"
506-
case _: DivergingImplicit | DivergingImplicit =>
507-
"Diverging Implicit"
508-
case result: ShadowedImplicit =>
509-
"Shadowed Implicit"
510-
case result: FailedImplicit =>
511-
"Failed Implicit"
512-
case result: AmbiguousImplicits =>
513-
"Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2)
514-
case _ =>
515-
"?Unknown Implicit Result?" + result.getClass
502+
case result: SearchFailure =>
503+
result.reason match {
504+
case _: NoMatchingImplicits => "No Matching Implicit"
505+
case _: DivergingImplicit => "Diverging Implicit"
506+
case _: ShadowedImplicit => "Shadowed Implicit"
507+
case result: AmbiguousImplicits =>
508+
"Ambiguous Implicit: " ~ toText(result.alt1.ref) ~ " and " ~ toText(result.alt2.ref)
509+
case _ =>
510+
"?Unknown Implicit Result?" + result.getClass
511+
}
516512
}
517513

518514
def toText(importInfo: ImportInfo): Text = {

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import TypeErasure.ErasedValueType
77
import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation
88
import StdNames.{nme, tpnme}
99
import ast.{Trees, untpd, tpd}
10-
import typer.{Namer, Inliner}
10+
import typer.{Namer, Inliner, Implicits}
1111
import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType}
1212
import Trees._
1313
import TypeApplications._
@@ -332,6 +332,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
332332
def toTextCore(tree: Tree): Text = tree match {
333333
case id: Trees.BackquotedIdent[_] if !homogenizedView =>
334334
"`" ~ toText(id.name) ~ "`"
335+
case id: Trees.SearchFailureIdent[_] =>
336+
tree.typeOpt match {
337+
case reason: Implicits.SearchFailureType =>
338+
toText(id.name) ~ "implicitly[" ~ toText(reason.expectedType) ~ "]"
339+
case _ =>
340+
toText(id.name)
341+
}
335342
case Ident(name) =>
336343
tree.typeOpt match {
337344
case tp: NamedType if name != nme.WILDCARD =>

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

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,33 +1031,43 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
10311031
tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
10321032
}
10331033

1034-
/** In a set of overloaded applicable alternatives, is `alt1` at least as good as
1035-
* `alt2`? Also used for implicits disambiguation.
1034+
/** Compare owner inheritance level.
1035+
* @param sym1 The first owner
1036+
* @param sym2 The second owner
1037+
* @return 1 if `sym1` properly derives from `sym2`
1038+
* -1 if `sym2` properly derives from `sym1`
1039+
* 0 otherwise
1040+
* Module classes also inherit the relationship from their companions.
1041+
*/
1042+
def compareOwner(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Int =
1043+
if (sym1 == sym2) 0
1044+
else if (sym1 isSubClass sym2) 1
1045+
else if (sym2 isSubClass sym1) -1
1046+
else if (sym2 is Module) compareOwner(sym1, sym2.companionClass)
1047+
else if (sym1 is Module) compareOwner(sym1.companionClass, sym2)
1048+
else 0
1049+
1050+
/** Compare to alternatives of an overloaded call or an implicit search.
10361051
*
10371052
* @param alt1, alt2 Non-overloaded references indicating the two choices
10381053
* @param level1, level2 If alternatives come from a comparison of two contextual
10391054
* implicit candidates, the nesting levels of the candidates.
10401055
* In all other cases the nesting levels are both 0.
1056+
* @return 1 if 1st alternative is preferred over 2nd
1057+
* -1 if 2nd alternative is preferred over 1st
1058+
* 0 if neither alternative is preferred over the other
10411059
*
1042-
* An alternative A1 is "as good as" an alternative A2 if it wins or draws in a tournament
1043-
* that awards one point for each of the following
1060+
* An alternative A1 is preferred over an alternative A2 if it wins in a tournament
1061+
* that awards one point for each of the following:
10441062
*
10451063
* - A1 is nested more deeply than A2
10461064
* - The nesting levels of A1 and A2 are the same, and A1's owner derives from A2's owner
10471065
* - A1's type is more specific than A2's type.
10481066
*/
1049-
def isAsGood(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Boolean = track("isAsGood") { trace(i"isAsGood($alt1, $alt2)", overload) {
1067+
def compare(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {
10501068

10511069
assert(alt1 ne alt2)
10521070

1053-
/** Is class or module class `sym1` derived from class or module class `sym2`?
1054-
* Module classes also inherit the relationship from their companions.
1055-
*/
1056-
def isDerived(sym1: Symbol, sym2: Symbol): Boolean =
1057-
if (sym1 isSubClass sym2) true
1058-
else if (sym2 is Module) isDerived(sym1, sym2.companionClass)
1059-
else (sym1 is Module) && isDerived(sym1.companionClass, sym2)
1060-
10611071
/** Is alternative `alt1` with type `tp1` as specific as alternative
10621072
* `alt2` with type `tp2` ?
10631073
*
@@ -1165,55 +1175,56 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11651175

11661176
val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol
11671177
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
1178+
val ownerScore =
1179+
if (nesting1 > nesting2) 1
1180+
else if (nesting1 < nesting2) -1
1181+
else compareOwner(owner1, owner2)
1182+
11681183
val tp1 = stripImplicit(alt1.widen)
11691184
val tp2 = stripImplicit(alt2.widen)
1170-
1171-
def winsOwner1 =
1172-
nesting1 > nesting2 || nesting1 == nesting2 && isDerived(owner1, owner2)
11731185
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1174-
def winsOwner2 =
1175-
nesting2 > nesting1 || nesting1 == nesting2 && isDerived(owner2, owner1)
11761186
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
11771187

1178-
overload.println(i"isAsGood($alt1, $alt2)? $tp1 $tp2 $winsOwner1 $winsType1 $winsOwner2 $winsType2")
1179-
1180-
// Assume the following probabilities:
1181-
//
1182-
// P(winsOwnerX) = 2/3
1183-
// P(winsTypeX) = 1/3
1184-
//
1185-
// Then the call probabilities of the 4 basic operations are as follows:
1186-
//
1187-
// winsOwner1: 1/1
1188-
// winsOwner2: 1/1
1189-
// winsType1 : 7/9
1190-
// winsType2 : 4/9
1191-
1192-
if (winsOwner1) /* 6/9 */ !winsOwner2 || /* 4/9 */ winsType1 || /* 8/27 */ !winsType2
1193-
else if (winsOwner2) /* 2/9 */ winsType1 && /* 2/27 */ !winsType2
1194-
else /* 1/9 */ winsType1 || /* 2/27 */ !winsType2
1188+
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
1189+
1190+
if (ownerScore == 1)
1191+
if (winsType1 || !winsType2) 1 else 0
1192+
else if (ownerScore == -1)
1193+
if (winsType2 || !winsType1) -1 else 0
1194+
else if (winsType1)
1195+
if (winsType2) 0 else 1
1196+
else
1197+
if (winsType2) -1 else 0
11951198
}}
11961199

11971200
def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
11981201
alts match {
11991202
case Nil => alts
12001203
case _ :: Nil => alts
1204+
case alt1 :: alt2 :: Nil =>
1205+
compare(alt1, alt2) match {
1206+
case 1 => alt1 :: Nil
1207+
case -1 => alt2 :: Nil
1208+
case 0 => alts
1209+
}
12011210
case alt :: alts1 =>
1202-
def winner(bestSoFar: TermRef, alts: List[TermRef]): TermRef = alts match {
1211+
def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match {
12031212
case alt :: alts1 =>
1204-
winner(if (isAsGood(alt, bestSoFar)) alt else bestSoFar, alts1)
1205-
case nil =>
1206-
bestSoFar
1213+
compare(previous.head, alt) match {
1214+
case 1 => survivors(previous, alts1)
1215+
case -1 => survivors(alt :: previous.tail, alts1)
1216+
case 0 => survivors(alt :: previous, alts1)
1217+
}
1218+
case Nil => previous
12071219
}
1208-
val best = winner(alt, alts1)
1220+
val best :: rest = survivors(alt :: Nil, alts1)
12091221
def asGood(alts: List[TermRef]): List[TermRef] = alts match {
12101222
case alt :: alts1 =>
1211-
if ((alt eq best) || !isAsGood(alt, best)) asGood(alts1)
1212-
else alt :: asGood(alts1)
1223+
if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1)
12131224
case nil =>
12141225
Nil
12151226
}
1216-
best :: asGood(alts)
1227+
best :: asGood(rest)
12171228
}
12181229
}
12191230

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,8 @@ trait Dynamic { self: Typer with Applications =>
150150
fail(i"""takes too many parameters.
151151
|Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
152152
else {
153-
def issueError(msgFn: String => String): Unit = ctx.error(msgFn(""), tree.pos)
154153
val ctags = tpe.paramInfos.map(pt =>
155-
inferImplicitArg(defn.ClassTagType.appliedTo(pt :: Nil), issueError, tree.pos.endPos))
154+
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos))
156155
structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType())
157156
}
158157
case tpe: ValueType =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ object ErrorReporting {
104104

105105
def patternConstrStr(tree: Tree): String = ???
106106

107-
def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = {
107+
def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = {
108108
val normTp = normalize(tree.tpe, pt)
109109
val treeTp = if (normTp <:< pt) tree.tpe else normTp
110110
// use normalized type if that also shows an error, original type otherwise
111-
errorTree(tree, typeMismatchMsg(treeTp, pt, implicitFailure.postscript))
111+
errorTree(tree, typeMismatchMsg(treeTp, pt, implicitFailure.whyNoConversion))
112112
}
113113

114114
/** A subtype log explaining why `found` does not conform to `expected` */

0 commit comments

Comments
 (0)