Skip to content

Commit

Permalink
Prune polymorphic implicits more aggressively
Browse files Browse the repository at this point in the history
This is a backport to 2.12.x of,

  #6580

In rankImplicits, before we attempt to fully typecheck the pending
candidate implicit, we first attempt to partially instantiate type
variables in both the candidate and the target type and check for
compatibility. If the compatibility check fails we can immediately prune
the the candidate without having to fully typecheck it.

In the kinds of implicit searches typical of the inductive style found
in shapeless and related libraries this can result in a drastic
reduction in the search space and a corresponding reduction in compile
times.

This commit is much simpler, more generally applicable, and less
invasive than my earlier work on inductive implicits in,

  #6481

which was doing similar pruning almost by accident. It turns out that
almost all of the speedups in that earlier PR were due to the pruning
rather than to any special casing of inductive patterns.

The compilation benchmark (a shapeless-style "select element by type
from a large HList") from that PR is carried over here (in
test/induction) and shows the same performance improvements,

  1) baseline - scalac 2.12.5
  2)            scalac 2.12.5 with matchesPtInst

               (1)   (2)
  HList Size
   50           4     3
  100           7     4
  150          14     4
  200          25     5
  250          46     5
  300          74     6
  350         118     8
  400         183    10
  450         298    12
  500         421    15

           Compile time in seconds

As an added bonus users of shapeless and shapeless based libraries which
use shapeless's Lazy type will see benefits immediately without needing
to wait for and port to byname implicit arguments.
  • Loading branch information
milessabin committed Apr 30, 2018
1 parent 689cd08 commit af4ffa8
Show file tree
Hide file tree
Showing 4 changed files with 661 additions and 2 deletions.
38 changes: 37 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Expand Up @@ -563,6 +563,39 @@ trait Implicits {
}
}

private def matchesPtInst(info: ImplicitInfo): Boolean = {
// the pt for views can have embedded unification type variables or BoundedWildcardTypes which
// can't be solved for. Rather than attempt to patch things up later we just skip those cases
// altogether.
def isInstantiable =
!isView || (wildPt.isGround && !wildPt.exists { case _: BoundedWildcardType => true ; case _ => false })

info.tpe match {
case PolyType(tparams, restpe) if isInstantiable =>
val tp = ApproximateDependentMap(restpe)
val allUndetparams = (undetParams ++ tparams).distinct
try {
val tvars = allUndetparams map freshVar
val tpInstantiated = tp.instantiateTypeParams(allUndetparams, tvars)
if(!matchesPt(tpInstantiated, wildPt, allUndetparams)) false
else {
val targs = solvedTypes(tvars, allUndetparams, allUndetparams map varianceInType(wildPt), upper = false, lubDepth(tpInstantiated :: wildPt :: Nil))
if (!checkBounds(EmptyTree, NoPrefix, NoSymbol, allUndetparams, targs, "inferred ")) false
else {
val AdjustedTypeArgs(okParams, okArgs) = adjustTypeArgs(allUndetparams, tvars, targs)
val remainingUndet = allUndetparams diff okParams
val tpSubst = deriveTypeWithWildcards(remainingUndet)(tp.instantiateTypeParams(okParams, okArgs))
val ptSubst = deriveTypeWithWildcards(remainingUndet)(pt.instantiateTypeParams(okParams, okArgs))
matchesPt(tpSubst, ptSubst, remainingUndet)
}
}
} catch {
case _: NoInstance => false
}
case _ => true
}
}

/** Capturing the overlap between isPlausiblyCompatible and normSubType.
* This is a faithful translation of the code which was there, but it
* seems likely the methods are intended to be even more similar than
Expand Down Expand Up @@ -971,7 +1004,10 @@ trait Implicits {
}
)

val typedFirstPending = typedImplicit(firstPending, ptChecked = true, isLocalToCallsite)
val typedFirstPending =
if(matchesPtInst(firstPending))
typedImplicit(firstPending, ptChecked = true, isLocalToCallsite)
else SearchFailure

// Pass the errors to `DivergentImplicitRecovery` so that it can note
// the first `DivergentImplicitTypeError` that is being propagated
Expand Down
2 changes: 1 addition & 1 deletion test/files/neg/divergent-implicit.check
Expand Up @@ -4,7 +4,7 @@ divergent-implicit.scala:4: error: type mismatch;
val x1: String = 1
^
divergent-implicit.scala:5: error: diverging implicit expansion for type Int => String
starting with method $conforms in object Predef
starting with method cast in object Test1
val x2: String = cast[Int, String](1)
^
divergent-implicit.scala:14: error: type mismatch;
Expand Down

0 comments on commit af4ffa8

Please sign in to comment.