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)
}