def localTypeInference()

in scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/api/InferUtil.scala [581:782]


  def localTypeInference(
    retType:                  ScType,
    params:                   Seq[Parameter],
    exprs:                    Seq[Expression],
    typeParams:               Seq[TypeParameter],
    shouldUndefineParameters: Boolean = true,
    canThrowSCE:              Boolean = false,
    filterTypeParams:         Boolean = true
  )(implicit context: Context): ScTypePolymorphicType =
    localTypeInferenceWithApplicabilityExt(
      retType,
      params,
      exprs,
      typeParams,
      shouldUndefineParameters,
      canThrowSCE,
      filterTypeParams
    )._1

  class SafeCheckException extends ControlThrowable

  def localTypeInferenceWithApplicabilityExt(
    retType:                  ScType,
    params:                   Seq[Parameter],
    exprs:                    Seq[Expression],
    typeParams:               Seq[TypeParameter],
    shouldUndefineParameters: Boolean               = true,
    canThrowSCE:              Boolean               = false,
    filterTypeParams:         Boolean               = true,
    paramSubst:               Option[ScSubstitutor] = None
  )(implicit context: Context): (ScTypePolymorphicType, ApplicabilityCheckResult) = {
    implicit val projectContext: ProjectContext = retType.projectContext

    val typeParamIds = typeParams.map(_.typeParamId).toSet
    def hasRecursiveTypeParams(tpe: ScType): Boolean = tpe.hasRecursiveTypeParameters(typeParamIds)

    // See SCL-3052, SCL-3058
    // This corresponds to use of `isCompatible` in `Infer#methTypeArgs` in scalac, where `isCompatible` uses `weak_<:<`
    val undefSubst: ScSubstitutor =
      if (shouldUndefineParameters) ScSubstitutor.bind(typeParams)(UndefinedType(_))
      else                          ScSubstitutor.empty

    val eTpeSubst = paramSubst.getOrElse(
      ScTypePolymorphicType(retType, typeParams).abstractTypeSubstitutor
    )

    val paramsWithUndefTypes = params.map(
      p =>
        p.copy(
          paramType    = undefSubst(p.paramType),
          expectedType = eTpeSubst(p.paramType),
          defaultType  = p.defaultType.map(undefSubst)
        )
    )

    val conformanceResult @ ApplicabilityCheckResult(problems, constraints, _, _) =
      Compatibility.checkMethodApplicability(
        paramsWithUndefTypes,
        exprs,
        withImplicits = true,
        shapesOnly    = false
      )

    val tpe = if (problems.isEmpty) {
      constraints.substitutionBounds(canThrowSCE) match {
        case Some(bounds @ SubstitutionBounds(_, lowerMap, upperMap)) =>
          val unSubst = bounds.substitutor
          if (!filterTypeParams) {

            def combineBounds(tp: TypeParameter, isLower: Boolean): ScType = {
              val bound        = if (isLower) tp.lowerType else tp.upperType
              val substedBound = unSubst(bound)
              val boundsMap    = if (isLower) lowerMap else upperMap

              val combine: (ScType, ScType) => ScType = if (isLower) _ lub _ else _ glb _

              boundsMap.get(tp.typeParamId) match {
                case Some(fromMap) =>
                  val mayCombine = !substedBound.equiv(fromMap) &&
                    !substedBound.hasRecursiveTypeParameters(typeParamIds)

                  if (mayCombine) combine(substedBound, fromMap)
                  else            fromMap
                case _ => substedBound
              }
            }

            val undefiningSubstitutor = ScSubstitutor.bind(typeParams)(UndefinedType(_))

            ScTypePolymorphicType(retType, typeParams.map { tp =>
              val lower = combineBounds(tp, isLower = true)
              val upper = combineBounds(tp, isLower = false)

              val boundsConformanceCheck =
                undefiningSubstitutor(lower).conforms(
                  undefiningSubstitutor(upper),
                  ConstraintSystem.empty,
                  checkWeak = true
                )

              if (canThrowSCE && !boundsConformanceCheck.isRight)
                throw new SafeCheckException

              TypeParameter(
                tp.psiTypeParameter, /* doesn't important here */
                tp.typeParameters,
                lower,
                upper
              )
            })
          } else {

            def addConstraints(un: ConstraintSystem, tp: TypeParameter): ConstraintSystem = {
              val typeParamId  = tp.typeParamId
              val substedLower = unSubst(tp.lowerType)
              val substedUpper = unSubst(tp.upperType)

              var result = un

              if (un.isApplicable(typeParamId) || substedLower != Nothing) {
                //todo: add only one of them according to variance

                //add constraints for tp from its' bounds
                if (!substedLower.isNothing && !hasRecursiveTypeParams(substedLower)) {
                  result = result.withLower(typeParamId, substedLower)
                    .withTypeParamId(typeParamId)
                }
                if (!substedUpper.isAny && !hasRecursiveTypeParams(substedUpper)) {
                  result = result.withUpper(typeParamId, substedUpper)
                    .withTypeParamId(typeParamId)
                }

                val lowerTpId = substedLower.asOptionOf[TypeParameterType].map(_.typeParamId).filter(typeParamIds.contains)
                val upperTpId = substedUpper.asOptionOf[TypeParameterType].map(_.typeParamId).filter(typeParamIds.contains)

                val substedTp = unSubst(TypeParameterType(tp))

                //add constraints for tp bounds from tp substitution
                if (!hasRecursiveTypeParams(substedTp)) {
                  upperTpId.foreach { id =>
                    result = result.withLower(id, substedTp)
                      .withTypeParamId(id)
                  }
                  lowerTpId.foreach { id =>
                    result = result.withUpper(id, substedTp)
                      .withTypeParamId(id)
                  }
                }
              }

              result
            }

            val newConstraints = typeParams.foldLeft(constraints)(addConstraints)

            val notInferred =
              if (!retType.isValue) Seq.empty
              else
                typeParams.filter(tp =>
                  tp.varianceInType(retType).isContravariant &&
                    !newConstraints.isApplicable(tp.typeParamId)
                )

            val contrSubst = ScSubstitutor.bind(notInferred)(tp => unSubst(tp.upperType))

            import org.jetbrains.plugins.scala.lang.psi.types.recursiveUpdate.SubtypeUpdater._

            def updateWithSubst(sub: ScSubstitutor): ScTypePolymorphicType = ScTypePolymorphicType(
              sub(retType),
              typeParams.filter { tp =>
                val removeMe = newConstraints.isApplicable(tp.typeParamId)

                if (removeMe && canThrowSCE) {
                  tp.psiTypeParameter match {
                    case typeParam: ScTypeParam =>
                      val tpt     = TypeParameterType(typeParam)
                      val substed = sub(tpt)

                      val kindsMatch =
                        tpt.typeParameters.isEmpty ||
                          substed.isAny ||
                          TypeVariableUnification.unifiableKinds(tpt, substed)

                      if (!kindsMatch) throw new SafeCheckException
                    case _ => ()
                  }
                }
                !removeMe
              }.map(_.update(sub))
            )

            newConstraints match {
              case ConstraintSystem(substitutor) => updateWithSubst(substitutor.followed(contrSubst))
              case _ if !canThrowSCE             => updateWithSubst(unSubst.followed(contrSubst))
              case _                             => throw new SafeCheckException
            }
          }
        case None => throw new SafeCheckException
      }
    } else ScTypePolymorphicType(retType, typeParams)
    (tpe, conformanceResult)
  }