in scala/scala-impl/src/org/jetbrains/plugins/scala/annotator/OverridingAnnotator.scala [145:384]
private def checkOverrideMembers[Res](
namedElement: ScNamedElement,
member: ScMember,
superSignaturesWithSelfType: Seq[Res],
superSignatures: Seq[Res],
isConcrete: Res => Boolean,
isExtension: Res => Boolean,
memberType: String
)(implicit
holder: ScalaAnnotationHolder
): Unit = {
implicit val context: Context = Context(namedElement)
import lang.lexer.{ScalaModifier, ScalaTokenTypes}
import ScalaModifier.{OVERRIDE, Override}
import quickfix.ModifierQuickFix._
val memberNameId = namedElement.nameId
val isInScala3 = namedElement.isInScala3File
if (superSignaturesWithSelfType.isEmpty) {
if (member.hasModifierProperty(OVERRIDE)) {
holder.createErrorAnnotation(
memberNameId,
ScalaBundle.message("member.overrides.nothing", memberType, namedElement.name),
new Remove(member, memberNameId, Override) +:
PullUpQuickFix(member, namedElement.name).toSeq
)
}
} else {
val isMismatchedExtension =
namedElement match {
case fn: ScFunction if fn.isExtensionMethod =>
if (superSignatures.exists(!isExtension(_))) {
holder.createErrorAnnotation(
memberNameId,
ScalaBundle.message("extension.method.overrides.regular", namedElement.name)
)
true
} else false
case _ =>
if (superSignatures.exists(isExtension)) {
holder.createErrorAnnotation(
memberNameId,
ScalaBundle.message("regular.method.overrides.extension", namedElement.name)
)
true
} else false
}
if (isConcreteElement(namedElement.nameContext)) {
val hasConcreteSuper = superSignatures.exists(isConcrete)
// Mill build files make override optional, so don't error if the keyword is missing
val isMillFile = namedElement.containingScalaFile.exists(_.isMillFile)
if (!isMillFile && hasConcreteSuper && !member.hasModifierProperty(OVERRIDE)) {
val maybeQuickFix: Option[Add] = namedElement match {
case param: ScClassParameter if param.isCaseClassPrimaryParameter && !(param.isVal || param.isVar) =>
superSignaturesWithSelfType.headOption.collect {
case signature: TermSignature => signature.namedElement
}.flatMap { element =>
import ScalaTokenTypes.{kVAL, kVAR}
element.nameContext match {
case parameter: ScClassParameter =>
val keywordElementType =
if (parameter.isValEffectively) kVAL
else kVAR
Some(keywordElementType)
case _: ScValue | _: ScFunction => Some(kVAL)
case _: ScVariable => Some(kVAR)
case _ => None
}
}.map {
new AddOverrideWithKeyword(member, memberNameId, _)
}
case _ => Some(new Add(member, memberNameId, Override))
}
holder.createErrorAnnotation(
memberNameId,
ScalaBundle.message("member.needs.override.modifier", memberType, namedElement.name),
maybeQuickFix
)
}
//fix for SCL-7831
var overridesFinal = false
for (signature <- superSignatures if !overridesFinal) {
val e =
signature match {
case signature: TermSignature => signature.namedElement
case _ => signature
}
e match {
case member: ScMember if member.isEffectivelyFinal =>
overridesFinal = true
case owner1: PsiModifierListOwner if owner1.hasFinalModifier =>
overridesFinal = true
case _ =>
}
}
if (overridesFinal) {
holder.createErrorAnnotation(memberNameId,
ScalaBundle.message("can.not.override.final", memberType, namedElement.name))
}
def annotateVarFromVal(): Unit = {
def addAnnotation(): Unit = {
holder.createErrorAnnotation(memberNameId,
ScalaBundle.message("var.cannot.override.val", namedElement.name))
}
for (signature <- superSignatures) {
signature match {
case s: TermSignature =>
s.namedElement match {
case f: ScFieldId if f.isVal => addAnnotation()
case rp: ScBindingPattern if rp.isVal => addAnnotation()
case cp: ScClassParameter if cp.isVal => addAnnotation()
case _ =>
}
case _ =>
}
}
}
def annotateFunFromValOrVar(): Unit = {
def annotVal(): Unit = {
holder.createErrorAnnotation(memberNameId,
ScalaBundle.message("member.cannot.override.val", namedElement.name))
}
for (signature <- superSignatures) {
signature match {
case s: TermSignature =>
s.namedElement match {
case rp: ScBindingPattern if rp.isVal => annotVal()
case cp: ScClassParameter if cp.isVal => annotVal()
case f: ScFieldId if f.isVal => annotVal()
case _ =>
}
case _ =>
}
}
}
namedElement match {
case _: ScFunctionDefinition => annotateFunFromValOrVar()
case inNameContext(_: ScVariable) => annotateVarFromVal()
case cp: ScClassParameter if cp.isVar => annotateVarFromVal()
case _ =>
}
}
def effectiveParams(fun: ScFunction) = fun.parameterClausesWithExtension().flatMap(_.effectiveParameters)
def overrideTypeMatchesBase(baseType: ScType, overType: ScType, s: TermSignature, baseName: String): Boolean = {
val renameSubst = (s.namedElement, namedElement) match {
case (sFun: ScFunction, mFun: ScFunction) if effectiveParams(sFun).length == effectiveParams(mFun).length &&
sFun.typeParameters.length == mFun.typeParameters.length =>
val sParams = effectiveParams(sFun)
val mParams = effectiveParams(mFun)
val sTypeParams = sFun.typeParametersWithExtension()
val mTypeParams = mFun.typeParametersWithExtension()
if (sParams.size != mParams.size || sTypeParams.size != mTypeParams.size)
ScSubstitutor.empty
else {
val typeParamSubst = ScSubstitutor.bind(sTypeParams, mTypeParams)(TypeParameterType(_))
val paramTypesSubst = ScSubstitutor.paramToParam(sParams, mParams)
typeParamSubst.followed(paramTypesSubst)
}
case _ => ScSubstitutor.empty
}
val fullSubst = s.substitutor.followed(renameSubst)
val actualType =
if (s.name == baseName + "_=") {
overType match {
case ParameterizedType(des, args) if des.canonicalText == "_root_.scala.Function1" => args.head
case _ => return true
}
} else renameSubst(overType)
val actualBase = fullSubst(baseType)
def allowEmptyParens(e: ScNamedElement): Boolean = e match {
case _: ScClassParameter => true
case _ =>
e.nameContext match {
case v: ScValueOrVariable =>
v.typeElement.isDefined || PropertyMethods.isBeanProperty(v)
case _ => false
}
}
def isBeanProperty(e: ScNamedElement): Boolean =
e.nameContext match {
case v: ScValueOrVariable => PropertyMethods.isBeanProperty(v)
case p: ScClassParameter => PropertyMethods.isBeanProperty(p)
case _ => false
}
actualType.conforms(actualBase) || ((actualBase, actualType, namedElement) match {
/* 5.1.3.3 M defines a parameterless method and M′ defines a method with an empty parameter list () or vice versa. */
case (ParameterizedType(des, args), _, _: ScFunction) if des.canonicalText == "_root_.scala.Function0" && !isInScala3 =>
actualType.conforms(args.head)
case (aType, ParameterizedType(des, args), _: ScFunction) if des.canonicalText == "_root_.scala.Function0" =>
aType.conforms(args.head)
case (ParameterizedType(des, args), _, patOrClassParam) =>
if (des.canonicalText == "_root_.scala.Function0" && allowEmptyParens(patOrClassParam) && !isInScala3)
actualType.conforms(args.head)
// @BeanProperty setter SCL-14462
else if (des.canonicalText == "_root_.scala.Function1" && isBeanProperty(patOrClassParam))
true // actualType.conforms(args.head) // looks like just "true" is enough
else false
case _ => false
})
}
implicit val tpc: TypePresentationContext = TypePresentationContext(memberNameId)
if (!isMismatchedExtension) {
for {
overridingType <- typeForSigElement(namedElement)
superSig <- superSignatures.filterByType[TermSignature]
baseType <- typeForSigElement(superSig.namedElement)
if !overrideTypeMatchesBase(baseType, overridingType, superSig, superSig.namedElement.name)
} {
holder.createErrorAnnotation(
memberNameId,
ScalaBundle.message(
"override.types.not.conforming",
overridingType.presentableText,
baseType.presentableText
)
)
}
}
}
}