Skip to content
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

Rework implicit search #3421

Merged
merged 30 commits into from Nov 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fd7ba2b
Fix #3396: Abort implicit search if result does not match
odersky Nov 1, 2017
fd70c61
Fix typo in comment
odersky Nov 1, 2017
2d4efa0
Reflect nesting of lazy_implicit values in their levels
odersky Nov 3, 2017
d7c8604
Propagate implicit ambiguity and divergence failures
odersky Nov 3, 2017
c311bf3
Move iterator-from test to pending
odersky Nov 3, 2017
beb90b4
Add test
odersky Nov 3, 2017
c1c9198
Refine order in which implicit candidates are tried
odersky Nov 3, 2017
a32b4b8
The great implicits re-org
odersky Nov 5, 2017
e1cf2c9
Add @sharable to global data
odersky Nov 5, 2017
7da033b
Disable refined preference scheme for implicits
odersky Nov 5, 2017
e6ffe35
Bring back iterator-from from pending
odersky Nov 6, 2017
d06d5ae
Replace isAsGood with ternary operator
odersky Nov 6, 2017
d8028de
WIP: Try out some alternatives
odersky Nov 6, 2017
2270899
New scheme for handling implicits
odersky Nov 6, 2017
01d8dfb
Cleanups
odersky Nov 6, 2017
83f86ab
Fix doc comment
odersky Nov 6, 2017
2df8031
Fix test comment
odersky Nov 6, 2017
50bf462
Remove stray text in comment
odersky Nov 6, 2017
ebb4ba2
Avoid repeated `tpd.` prefixes in Implicits.scala
odersky Nov 7, 2017
15744fb
Add some doc comments
odersky Nov 7, 2017
0e8a74d
Disable numeric value tie break
odersky Nov 7, 2017
9c1d942
New test exercising implicit function types
odersky Nov 7, 2017
0bae8a3
New test exercising implicit function types
odersky Nov 7, 2017
2144367
Implement negation of implicit searches
odersky Nov 13, 2017
03779a1
Fixes and documentation for Not class
odersky Nov 14, 2017
98c4b7d
Fix handling divergent implicits
odersky Nov 14, 2017
39b4ec2
Address reviewers comments
odersky Nov 15, 2017
66b0d9a
Fix typo
odersky Nov 15, 2017
0da21e1
Avoid infinite loop
odersky Nov 15, 2017
db1b09a
Simplify control flow in rank method
odersky Nov 15, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Expand Up @@ -145,9 +145,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 @@ -449,6 +449,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 @@ -948,6 +953,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 @@ -282,6 +282,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 @@ -1981,7 +1981,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 @@ -3641,22 +3641,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