Skip to content

Commit

Permalink
Merge pull request #3421 from dotty-staging/fix-strawan
Browse files Browse the repository at this point in the history
Rework implicit search
  • Loading branch information
odersky committed Nov 15, 2017
2 parents 7759e00 + db1b09a commit 745fae6
Show file tree
Hide file tree
Showing 23 changed files with 741 additions and 330 deletions.
10 changes: 8 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Expand Up @@ -69,9 +69,9 @@ object Trees {
}

/** A unique identifier for this tree. Used for debugging, and potentially
* tracking presentation compiler interactions
* tracking presentation compiler interactions.
*/
private var myUniqueId: Int = nxId
@sharable private var myUniqueId: Int = nxId

def uniqueId = myUniqueId

Expand Down Expand Up @@ -370,6 +370,11 @@ object Trees {
override def toString = s"BackquotedIdent($name)"
}

class SearchFailureIdent[-T >: Untyped] private[ast] (name: Name)
extends Ident[T](name) {
override def toString = s"SearchFailureIdent($name)"
}

/** qualifier.name, or qualifier#name, if qualifier is a type */
case class Select[-T >: Untyped] private[ast] (qualifier: Tree[T], name: Name)
extends RefTree[T] {
Expand Down Expand Up @@ -830,6 +835,7 @@ object Trees {

type Ident = Trees.Ident[T]
type BackquotedIdent = Trees.BackquotedIdent[T]
type SearchFailureIdent = Trees.SearchFailureIdent[T]
type Select = Trees.Select[T]
type SelectWithSig = Trees.SelectWithSig[T]
type This = Trees.This[T]
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Expand Up @@ -247,6 +247,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

def Ident(name: Name): Ident = new Ident(name)
def BackquotedIdent(name: Name): BackquotedIdent = new BackquotedIdent(name)
def SearchFailureIdent(name: Name): SearchFailureIdent = new SearchFailureIdent(name)
def Select(qualifier: Tree, name: Name): Select = new Select(qualifier, name)
def SelectWithSig(qualifier: Tree, name: Name, sig: Signature): Select = new SelectWithSig(qualifier, name, sig)
def This(qual: Ident): This = new This(qual)
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Expand Up @@ -26,7 +26,6 @@ class ScalaSettings extends Settings.SettingGroup {
val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.")
val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding)
val explainTypes = BooleanSetting("-explain-types", "Explain type errors in more detail.")
val explainImplicits = BooleanSetting("-explain-implicits", "Explain implicit search errors in more detail.")
val explain = BooleanSetting("-explain", "Explain errors in more detail.")
val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.")
val help = BooleanSetting("-help", "Print a synopsis of standard options")
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Expand Up @@ -584,6 +584,12 @@ class Definitions {

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

lazy val NotType = ctx.requiredClassRef("scala.implicits.Not")
def NotClass(implicit ctx: Context) = NotType.symbol.asClass
def NotModule(implicit ctx: Context) = NotClass.companionModule

def Not_value(implicit ctx: Context) = NotModule.requiredMethod(nme.value)

lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")

// Annotation base classes
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TyperState.scala
Expand Up @@ -89,7 +89,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
private[this] var testReporter: StoreReporter = null

/** Test using `op`, restoring typerState to previous state afterwards */
def test(op: => Boolean): Boolean = {
def test[T](op: => T): T = {
val savedReporter = myReporter
val savedConstraint = myConstraint
val savedCommittable = myIsCommittable
Expand Down
21 changes: 12 additions & 9 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Expand Up @@ -1978,7 +1978,7 @@ object Types {
else candidate

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

class ErrorType private[Types] () extends FlexType {
def msg(implicit ctx: Context): Message =
ctx.errorTypeMsg.get(this) match {
case Some(msgFun) => msgFun()
case None => "error message from previous run no longer available"
}
abstract class ErrorType extends FlexType {
def msg(implicit ctx: Context): Message
}

object ErrorType {
def apply(msg: => Message)(implicit ctx: Context): ErrorType = {
val et = new ErrorType
val et = new ErrorType {
def msg(implicit ctx: Context): Message =
ctx.errorTypeMsg.get(this) match {
case Some(msgFun) => msgFun()
case None => "error message from previous run no longer available"
}
}
ctx.base.errorTypeMsg(et) = () => msg
et
}
}

object UnspecifiedErrorType extends ErrorType() {
object UnspecifiedErrorType extends ErrorType {
override def msg(implicit ctx: Context): Message = "unspecified error"
}

Expand Down
28 changes: 12 additions & 16 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Expand Up @@ -171,8 +171,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) }
case OrType(tp1, tp2) =>
changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) }
case _: ErrorType =>
"<error>"
case tp: ErrorType =>
s"<error ${tp.msg.msg}>"
case tp: WildcardType =>
if (tp.optBounds.exists) "(?" ~ toTextRHS(tp.bounds) ~ ")" else "?"
case NoType =>
Expand Down Expand Up @@ -202,8 +202,6 @@ class PlainPrinter(_ctx: Context) extends Printer {
ParamRefNameString(tp) ~ ".type"
case AnnotatedType(tpe, annot) =>
toTextLocal(tpe) ~ " " ~ toText(annot)
case AppliedType(tycon, args) =>
toTextLocal(tycon) ~ "[" ~ Text(args.map(argText), ", ") ~ "]"
case tp: TypeVar =>
if (tp.isInstantiated)
toTextLocal(tp.instanceOpt) ~ ("^" provided ctx.settings.YprintDebug.value)
Expand Down Expand Up @@ -501,18 +499,16 @@ class PlainPrinter(_ctx: Context) extends Printer {
def toText(result: SearchResult): Text = result match {
case result: SearchSuccess =>
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)
case _: NonMatchingImplicit | NoImplicitMatches =>
"NoImplicitMatches"
case _: DivergingImplicit | DivergingImplicit =>
"Diverging Implicit"
case result: ShadowedImplicit =>
"Shadowed Implicit"
case result: FailedImplicit =>
"Failed Implicit"
case result: AmbiguousImplicits =>
"Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2)
case _ =>
"?Unknown Implicit Result?" + result.getClass
case result: SearchFailure =>
result.reason match {
case _: NoMatchingImplicits => "No Matching Implicit"
case _: DivergingImplicit => "Diverging Implicit"
case _: ShadowedImplicit => "Shadowed Implicit"
case result: AmbiguousImplicits =>
"Ambiguous Implicit: " ~ toText(result.alt1.ref) ~ " and " ~ toText(result.alt2.ref)
case _ =>
"?Unknown Implicit Result?" + result.getClass
}
}

def toText(importInfo: ImportInfo): Text = {
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Expand Up @@ -7,7 +7,7 @@ import TypeErasure.ErasedValueType
import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation
import StdNames.{nme, tpnme}
import ast.{Trees, untpd, tpd}
import typer.{Namer, Inliner}
import typer.{Namer, Inliner, Implicits}
import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType}
import Trees._
import TypeApplications._
Expand Down Expand Up @@ -332,6 +332,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
def toTextCore(tree: Tree): Text = tree match {
case id: Trees.BackquotedIdent[_] if !homogenizedView =>
"`" ~ toText(id.name) ~ "`"
case id: Trees.SearchFailureIdent[_] =>
tree.typeOpt match {
case reason: Implicits.SearchFailureType =>
toText(id.name) ~ "implicitly[" ~ toText(reason.expectedType) ~ "]"
case _ =>
toText(id.name)
}
case Ident(name) =>
tree.typeOpt match {
case tp: NamedType if name != nme.WILDCARD =>
Expand Down
97 changes: 54 additions & 43 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Expand Up @@ -1031,33 +1031,43 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
}

/** In a set of overloaded applicable alternatives, is `alt1` at least as good as
* `alt2`? Also used for implicits disambiguation.
/** Compare owner inheritance level.
* @param sym1 The first owner
* @param sym2 The second owner
* @return 1 if `sym1` properly derives from `sym2`
* -1 if `sym2` properly derives from `sym1`
* 0 otherwise
* Module classes also inherit the relationship from their companions.
*/
def compareOwner(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Int =
if (sym1 == sym2) 0
else if (sym1 isSubClass sym2) 1
else if (sym2 isSubClass sym1) -1
else if (sym2 is Module) compareOwner(sym1, sym2.companionClass)
else if (sym1 is Module) compareOwner(sym1.companionClass, sym2)
else 0

/** Compare to alternatives of an overloaded call or an implicit search.
*
* @param alt1, alt2 Non-overloaded references indicating the two choices
* @param level1, level2 If alternatives come from a comparison of two contextual
* implicit candidates, the nesting levels of the candidates.
* In all other cases the nesting levels are both 0.
* @return 1 if 1st alternative is preferred over 2nd
* -1 if 2nd alternative is preferred over 1st
* 0 if neither alternative is preferred over the other
*
* An alternative A1 is "as good as" an alternative A2 if it wins or draws in a tournament
* that awards one point for each of the following
* An alternative A1 is preferred over an alternative A2 if it wins in a tournament
* that awards one point for each of the following:
*
* - A1 is nested more deeply than A2
* - The nesting levels of A1 and A2 are the same, and A1's owner derives from A2's owner
* - A1's type is more specific than A2's type.
*/
def isAsGood(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Boolean = track("isAsGood") { trace(i"isAsGood($alt1, $alt2)", overload) {
def compare(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {

assert(alt1 ne alt2)

/** Is class or module class `sym1` derived from class or module class `sym2`?
* Module classes also inherit the relationship from their companions.
*/
def isDerived(sym1: Symbol, sym2: Symbol): Boolean =
if (sym1 isSubClass sym2) true
else if (sym2 is Module) isDerived(sym1, sym2.companionClass)
else (sym1 is Module) && isDerived(sym1.companionClass, sym2)

/** Is alternative `alt1` with type `tp1` as specific as alternative
* `alt2` with type `tp2` ?
*
Expand Down Expand Up @@ -1165,55 +1175,56 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>

val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
val ownerScore =
if (nesting1 > nesting2) 1
else if (nesting1 < nesting2) -1
else compareOwner(owner1, owner2)

val tp1 = stripImplicit(alt1.widen)
val tp2 = stripImplicit(alt2.widen)

def winsOwner1 =
nesting1 > nesting2 || nesting1 == nesting2 && isDerived(owner1, owner2)
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
def winsOwner2 =
nesting2 > nesting1 || nesting1 == nesting2 && isDerived(owner2, owner1)
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)

overload.println(i"isAsGood($alt1, $alt2)? $tp1 $tp2 $winsOwner1 $winsType1 $winsOwner2 $winsType2")

// Assume the following probabilities:
//
// P(winsOwnerX) = 2/3
// P(winsTypeX) = 1/3
//
// Then the call probabilities of the 4 basic operations are as follows:
//
// winsOwner1: 1/1
// winsOwner2: 1/1
// winsType1 : 7/9
// winsType2 : 4/9

if (winsOwner1) /* 6/9 */ !winsOwner2 || /* 4/9 */ winsType1 || /* 8/27 */ !winsType2
else if (winsOwner2) /* 2/9 */ winsType1 && /* 2/27 */ !winsType2
else /* 1/9 */ winsType1 || /* 2/27 */ !winsType2
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")

if (ownerScore == 1)
if (winsType1 || !winsType2) 1 else 0
else if (ownerScore == -1)
if (winsType2 || !winsType1) -1 else 0
else if (winsType1)
if (winsType2) 0 else 1
else
if (winsType2) -1 else 0
}}

def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
alts match {
case Nil => alts
case _ :: Nil => alts
case alt1 :: alt2 :: Nil =>
compare(alt1, alt2) match {
case 1 => alt1 :: Nil
case -1 => alt2 :: Nil
case 0 => alts
}
case alt :: alts1 =>
def winner(bestSoFar: TermRef, alts: List[TermRef]): TermRef = alts match {
def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match {
case alt :: alts1 =>
winner(if (isAsGood(alt, bestSoFar)) alt else bestSoFar, alts1)
case nil =>
bestSoFar
compare(previous.head, alt) match {
case 1 => survivors(previous, alts1)
case -1 => survivors(alt :: previous.tail, alts1)
case 0 => survivors(alt :: previous, alts1)
}
case Nil => previous
}
val best = winner(alt, alts1)
val best :: rest = survivors(alt :: Nil, alts1)
def asGood(alts: List[TermRef]): List[TermRef] = alts match {
case alt :: alts1 =>
if ((alt eq best) || !isAsGood(alt, best)) asGood(alts1)
else alt :: asGood(alts1)
if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1)
case nil =>
Nil
}
best :: asGood(alts)
best :: asGood(rest)
}
}

Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Expand Up @@ -150,9 +150,8 @@ trait Dynamic { self: Typer with Applications =>
fail(i"""takes too many parameters.
|Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
else {
def issueError(msgFn: String => String): Unit = ctx.error(msgFn(""), tree.pos)
val ctags = tpe.paramInfos.map(pt =>
inferImplicitArg(defn.ClassTagType.appliedTo(pt :: Nil), issueError, tree.pos.endPos))
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos))
structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType())
}
case tpe: ValueType =>
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Expand Up @@ -104,11 +104,11 @@ object ErrorReporting {

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

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

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

0 comments on commit 745fae6

Please sign in to comment.