Skip to content

Commit 3a30e57

Browse files
committed
Optimize implicit plausibility check
Also fixes a 2.12 regression, implicit defs with multiple non-implicit param lists should be able to satisfy implicit searches for curried function types.
1 parent ed50887 commit 3a30e57

File tree

3 files changed

+111
-28
lines changed

3 files changed

+111
-28
lines changed

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,44 @@ trait Implicits {
221221
private var tpeCache: Type = null
222222
private var isErroneousCache: TriState = TriState.Unknown
223223

224+
var paramTypeInfoss: List[Array[Type]] = Nil
225+
var paramTypeSymss: List[Array[Symbol]] = Nil
226+
var resultType: Type = null
227+
224228
/** Computes member type of implicit from prefix `pre` (cached). */
225229
def tpe: Type = {
226-
if (tpeCache eq null) tpeCache = pre.memberType(sym)
230+
if (tpeCache eq null) {
231+
tpeCache = pre.memberType(sym)
232+
def loop(tp: Type): Unit = tp match {
233+
case PolyType(_, res) => loop(res)
234+
case mt @ MethodType(params, res) =>
235+
if (mt.isImplicit) loop(res)
236+
else {
237+
loop(res)
238+
val len = params.length
239+
var i = 0
240+
val paramTypeInfos = new Array[Type](len)
241+
val paramTypeSyms = new Array[Symbol](len)
242+
var ps = params
243+
while (i < len) {
244+
val paramTypeSym = ps.head.info match {
245+
case TypeRef(_, sym, _) if sym.isClass => sym
246+
case _ => NoSymbol
247+
}
248+
paramTypeInfos(i) = ps.head.info
249+
paramTypeSyms(i) = paramTypeSym
250+
i += 1
251+
ps = ps.tail
252+
}
253+
paramTypeInfoss ::= paramTypeInfos
254+
paramTypeSymss ::= paramTypeSyms
255+
res.finalResultType
256+
}
257+
case tp =>
258+
resultType = tp
259+
}
260+
loop(tpeCache.underlying)
261+
}
227262
tpeCache
228263
}
229264

@@ -386,9 +421,23 @@ trait Implicits {
386421
/** The type parameters to instantiate */
387422
val undetParams = if (isView) Nil else context.outer.undetparams
388423
val wildPt = approximate(pt)
389-
private val ptFunctionArity: Int = {
390-
val dealiased = pt.dealiasWiden
391-
if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.length - 1 else -1
424+
private var ptFunctionResult: Type = null
425+
private val ptFunctionSig: List[Array[Type]] = {
426+
var result: List[Array[Type]] = Nil
427+
def loop(tp: Type): Unit = {
428+
val dealiased = tp.dealiasWiden
429+
if (isFunctionTypeDirect(dealiased)) {
430+
val typeArgs = dealiased.typeArgs
431+
loop(typeArgs.last)
432+
val args = new Array[Type](typeArgs.length - 1)
433+
typeArgs.copyToArray(args)
434+
result ::= args
435+
} else {
436+
ptFunctionResult = tp
437+
}
438+
}
439+
loop(pt)
440+
result
392441
}
393442

394443
private val stableRunDefsForImport = currentRun.runDefinitions
@@ -423,8 +472,8 @@ trait Implicits {
423472
} else isStrictlyMoreSpecific(info1.tpe, info2.tpe, info1.sym, info2.sym)
424473
}
425474
}
426-
def isPlausiblyCompatible(tp: Type, pt: Type) = checkCompatibility(fast = true, tp, pt)
427-
def normSubType(tp: Type, pt: Type) = checkCompatibility(fast = false, tp, pt)
475+
def isPlausiblyCompatible(info: ImplicitInfo, pt: Type) = checkCompatibilityFast(info, info.tpe, pt)
476+
def normSubType(tp: Type, pt: Type) = checkCompatibilitySlow(tp, pt)
428477

429478
/** Does type `dtor` dominate type `dted`?
430479
* This is the case if the stripped cores `dtor1` and `dted1` of both types are
@@ -547,7 +596,7 @@ trait Implicits {
547596
private def matchesPtView(tp: Type, ptarg: Type, ptres: Type, undet: List[Symbol]): Boolean = tp match {
548597
case MethodType(p :: _, restpe) if p.isImplicit => matchesPtView(restpe, ptarg, ptres, undet)
549598
case MethodType(p :: Nil, restpe) => matchesArgRes(p.tpe, restpe, ptarg, ptres, undet)
550-
case ExistentialType(_, qtpe) => matchesPtView(normalize(qtpe), ptarg, ptres, undet)
599+
case ExistentialType(_, qtpe) => matchesPtView(qtpe, ptarg, ptres, undet)
551600
case Function1(arg1, res1) => matchesArgRes(arg1, res1, ptarg, ptres, undet)
552601
case _ => false
553602
}
@@ -569,7 +618,32 @@ trait Implicits {
569618
* they are: perhaps someone more familiar with the intentional distinctions
570619
* can examine the now much smaller concrete implementations below.
571620
*/
572-
private def checkCompatibility(fast: Boolean, tp0: Type, pt0: Type): Boolean = {
621+
private def checkCompatibilityFast(info: ImplicitInfo, tp0: Type, pt: Type): Boolean = {
622+
pt match {
623+
case tr @ TypeRef(pre, sym, args) =>
624+
if (sym.isAliasType) checkCompatibilityFast(info, tp0, pt.dealias)
625+
else if (sym.isAbstractType) checkCompatibilityFast(info, tp0, pt.bounds.lo)
626+
else {
627+
if (info.paramTypeInfoss == Nil)
628+
isPlausiblySubType(tp0, pt)
629+
else {
630+
val len = info.paramTypeInfoss.length
631+
val (ptFunctionSigPrefix, ptFunctionSigSuffix) = ptFunctionSig.splitAt(len)
632+
val paramssCompatible = info.paramTypeInfoss.corresponds(ptFunctionSigPrefix) {
633+
(infos, ptArgs) =>
634+
(infos.length == ptArgs.length) && infos.corresponds(ptArgs) { (info, ptArg) =>
635+
isPlausiblySubType(ptArg, info)
636+
}
637+
}
638+
val ptResidual = ptFunctionSigSuffix.foldRight(ptFunctionResult)((tps, res) => functionType(tps.toList, res))
639+
paramssCompatible && isPlausiblySubType(info.resultType, ptResidual)
640+
}
641+
}
642+
case _ => isPlausiblySubType(tp0, pt)
643+
}
644+
}
645+
646+
private def checkCompatibilitySlow(tp0: Type, pt0: Type): Boolean = {
573647
@tailrec def loop(tp: Type, pt: Type): Boolean = tp match {
574648
case mt @ MethodType(params, restpe) =>
575649
if (mt.isImplicit)
@@ -579,23 +653,15 @@ trait Implicits {
579653
if (sym.isAliasType) loop(tp, pt.dealias)
580654
else if (sym.isAbstractType) loop(tp, pt.bounds.lo)
581655
else {
582-
ptFunctionArity > 0 && hasLength(params, ptFunctionArity) && {
656+
hasLength(args, params.length + 1) && {
583657
var ps = params
584658
var as = args
585-
if (fast) {
586-
while (ps.nonEmpty && as.nonEmpty) {
587-
if (!isPlausiblySubType(as.head, ps.head.tpe))
588-
return false
589-
ps = ps.tail
590-
as = as.tail
591-
}
592-
} else {
593-
while (ps.nonEmpty && as.nonEmpty) {
594-
if (!(as.head <:< ps.head.tpe))
595-
return false
596-
ps = ps.tail
597-
as = as.tail
598-
}
659+
var i = 0
660+
while (ps.nonEmpty && as.nonEmpty) {
661+
if (!(as.head <:< ps.head.tpe))
662+
return false
663+
ps = ps.tail
664+
as = as.tail
599665
}
600666
ps.isEmpty && as.nonEmpty && {
601667
val lastArg = as.head
@@ -604,12 +670,12 @@ trait Implicits {
604670
}
605671
}
606672

607-
case _ => if (fast) false else tp <:< pt
673+
case _ => tp <:< pt
608674
}
609675
case NullaryMethodType(restpe) => loop(restpe, pt)
610676
case PolyType(_, restpe) => loop(restpe, pt)
611-
case ExistentialType(_, qtpe) => if (fast) loop(qtpe, pt) else normalize(tp) <:< pt // is !fast case needed??
612-
case _ => if (fast) isPlausiblySubType(tp, pt) else tp <:< pt
677+
case ExistentialType(_, qtpe) => tp <:< pt
678+
case _ => tp <:< pt
613679
}
614680
loop(tp0, pt0)
615681
}
@@ -876,7 +942,7 @@ trait Implicits {
876942
*/
877943
def survives(info: ImplicitInfo) = (
878944
!isIneligible(info) // cyclic, erroneous, shadowed, or specially excluded
879-
&& isPlausiblyCompatible(info.tpe, wildPt) // optimization to avoid matchesPt
945+
&& isPlausiblyCompatible(info, wildPt) // optimization to avoid matchesPt
880946
&& !shadower.isShadowed(info.name) // OPT rare, only check for plausible candidates
881947
&& matchesPt(info) // stable and matches expected type
882948
)

test/files/pos/t0438.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class Foo {
2-
implicit def pair2fun2[A, B, C](f: (A, B) => C) =
2+
implicit def pair2fun2[A, B, C](f: (A, B) => C): Tuple2[A, B] => C =
33
{p: (A, B) => f(p._1, p._2) }
44

55
def foo(f: ((Int, Int)) => Int) = f

test/files/pos/t10858.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import language.implicitConversions
2+
3+
class Test1 {
4+
implicit def foo(a: Int)(b: Int, c: Int): String = "" + a + b
5+
implicitly[Int => (Int, Int) => String]
6+
}
7+
8+
9+
class Test2 {
10+
implicit def foo(a: Int)(b: Int): String = "" + a + b
11+
implicitly[Int => (Int) => String]
12+
}
13+
14+
class Test3 {
15+
implicit def foo(a: Int)(b: Int)(c: String, e: String): String = "" + a + b
16+
implicitly[Int => (Int) => (String, String) => String]
17+
}

0 commit comments

Comments
 (0)