mirror of https://github.com/Wilfred/difftastic/
2159 lines
106 KiB
Scala
2159 lines
106 KiB
Scala
/*
|
|
* Scala (https://www.scala-lang.org)
|
|
*
|
|
* Copyright EPFL and Lightbend, Inc.
|
|
*
|
|
* Licensed under Apache License 2.0
|
|
* (http://www.apache.org/licenses/LICENSE-2.0).
|
|
*
|
|
* See the NOTICE file distributed with this work for
|
|
* additional information regarding copyright ownership.
|
|
*/
|
|
|
|
package scala.tools.nsc
|
|
package typechecker
|
|
|
|
import scala.collection.mutable
|
|
import scala.collection.mutable.ListBuffer
|
|
import scala.reflect.internal.util.CodeAction
|
|
import scala.tools.nsc.Reporting.WarningCategory
|
|
import scala.tools.nsc.settings.ScalaVersion
|
|
import scala.tools.nsc.settings.NoScalaVersion
|
|
import symtab.Flags._
|
|
import transform.Transform
|
|
|
|
/** Post-attribution checking and transformation.
|
|
*
|
|
* This phase checks the following postconditions:
|
|
*
|
|
* - All overrides conform to rules.
|
|
* - All type arguments conform to bounds.
|
|
* - Every use of a type variable conforms to the variance annotation of that variable.
|
|
* - No forward reference to a term symbol extends beyond a value definition.
|
|
*
|
|
* It performs the following transformations:
|
|
*
|
|
* - Local modules are replaced by variables and classes.
|
|
* - Calls to case factory methods are replaced by new's.
|
|
* - Eliminate branches in a conditional if the condition is a constant.
|
|
*
|
|
* @author Martin Odersky
|
|
*/
|
|
abstract class RefChecks extends Transform {
|
|
|
|
val global: Global // need to repeat here because otherwise last mixin defines global as
|
|
// SymbolTable. If we had DOT this would not be an issue
|
|
|
|
import global._
|
|
import definitions._
|
|
import typer.typed
|
|
|
|
/** the following two members override abstract members in Transform */
|
|
val phaseName: String = "refchecks"
|
|
|
|
def newTransformer(unit: CompilationUnit): RefCheckTransformer =
|
|
new RefCheckTransformer(unit)
|
|
|
|
val toJavaRepeatedParam = SubstSymMap(RepeatedParamClass -> JavaRepeatedParamClass)
|
|
val toScalaRepeatedParam = SubstSymMap(JavaRepeatedParamClass -> RepeatedParamClass)
|
|
|
|
def accessFlagsToString(sym: Symbol) = flagsToString(
|
|
sym getFlag (PRIVATE | PROTECTED),
|
|
if (sym.hasAccessBoundary) "" + sym.privateWithin.name else ""
|
|
)
|
|
|
|
def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type): Boolean = (tp1.dealiasWiden, tp2.dealiasWiden) match {
|
|
case (MethodType(List(), rtp1), NullaryMethodType(rtp2)) => rtp1 <:< rtp2
|
|
case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) => rtp1 <:< rtp2
|
|
case (TypeRef(_, sym, _), _) if sym.isModuleClass => overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix)
|
|
case (_, TypeRef(_, sym, _)) if sym.isModuleClass => overridesTypeInPrefix(tp1, NullaryMethodType(tp2), prefix)
|
|
case _ => tp1 <:< tp2
|
|
}
|
|
|
|
private val separatelyCompiledScalaSuperclass = perRunCaches.newAnyRefMap[Symbol, Unit]()
|
|
final def isSeparatelyCompiledScalaSuperclass(sym: Symbol) = if (globalPhase.refChecked){
|
|
separatelyCompiledScalaSuperclass.contains(sym)
|
|
} else {
|
|
// conservative approximation in case someone in pre-refchecks phase asks for `exitingFields(someClass.info)`
|
|
// and we haven't run the refchecks tree transform which populates `separatelyCompiledScalaSuperclass`
|
|
false
|
|
}
|
|
|
|
class RefCheckTransformer(unit: CompilationUnit) extends AstTransformer {
|
|
private final val indent = " "
|
|
|
|
var localTyper: analyzer.Typer = typer
|
|
var currentApplication: Tree = EmptyTree
|
|
var inAnnotation: Boolean = false
|
|
var inPattern: Boolean = false
|
|
@inline final def savingInPattern[A](body: => A): A = {
|
|
val saved = inPattern
|
|
try body finally inPattern = saved
|
|
}
|
|
|
|
// Track symbols of the refinement's parents and the base at which we've checked them,
|
|
// as well as the entire refinement type seen at that base.
|
|
// No need to check the same symbols again in a base that's a subclass of a previously checked base
|
|
private val checkedCombinations = mutable.Map[List[Symbol], (Symbol, Type)]()
|
|
private def notYetCheckedOrAdd(rt: RefinedType, currentBase: Symbol) = {
|
|
val seen = checkedCombinations.get(rt.parents.map(_.typeSymbol)).exists {
|
|
case (prevBase, prevTp) => currentBase.isSubClass(prevBase) && rt =:= prevTp.asSeenFrom(currentBase.thisType, prevBase)
|
|
}
|
|
|
|
if (!seen) checkedCombinations.addOne((rt.parents.map(_.typeSymbol), (currentBase, rt)))
|
|
|
|
!seen
|
|
}
|
|
|
|
private def refchecksWarning(pos: Position, msg: String, cat: WarningCategory, actions: List[CodeAction] = Nil): Unit =
|
|
runReporting.warning(pos, msg, cat, currentOwner, actions)
|
|
|
|
// only one overloaded alternative is allowed to define default arguments
|
|
private def checkOverloadedRestrictions(clazz: Symbol, defaultClass: Symbol): Unit = {
|
|
// Using the default getters (such as methodName$default$1) as a cheap way of
|
|
// finding methods with default parameters. This way, we can limit the members to
|
|
// those with the DEFAULTPARAM flag, and infer the methods. Looking for the methods
|
|
// directly requires inspecting the parameter list of every one. That modification
|
|
// shaved 95% off the time spent in this method.
|
|
val defaultGetters = defaultClass.info.findMembers(excludedFlags = PARAM, requiredFlags = DEFAULTPARAM)
|
|
val defaultMethodNames = defaultGetters map (sym => nme.defaultGetterToMethod(sym.name))
|
|
|
|
defaultMethodNames.toList.distinct foreach { name =>
|
|
val methods = clazz.info.findMember(name, 0L, requiredFlags = METHOD, stableOnly = false).alternatives
|
|
def hasDefaultParam(tpe: Type): Boolean = tpe match {
|
|
case MethodType(params, restpe) => (params exists (_.hasDefault)) || hasDefaultParam(restpe)
|
|
case _ => false
|
|
}
|
|
val haveDefaults = methods.filter(sym => mexists(sym.info.paramss)(_.hasDefault) && !nme.isProtectedAccessorName(sym.name))
|
|
|
|
if (haveDefaults.lengthCompare(1) > 0) {
|
|
val owners = haveDefaults map (_.owner)
|
|
// constructors of different classes are allowed to have defaults
|
|
if (haveDefaults.exists(x => !x.isConstructor) || owners.distinct.size < haveDefaults.size) {
|
|
reporter.error(clazz.pos,
|
|
"in "+ clazz +
|
|
", multiple overloaded alternatives of "+ haveDefaults.head +
|
|
" define default arguments" + (
|
|
if (owners.forall(_ == clazz)) "."
|
|
else ".\nThe members with defaults are defined in "+owners.map(_.fullLocationString).mkString("", " and ", ".")
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for doomed attempt to overload applyDynamic
|
|
if (clazz isSubClass DynamicClass) {
|
|
for ((_, m1 :: m2 :: _) <- (clazz.info member nme.applyDynamic).alternatives groupBy (_.typeParams.length)) {
|
|
reporter.error(m1.pos, "implementation restriction: applyDynamic cannot be overloaded except by methods with different numbers of type parameters, e.g. applyDynamic[T1](method: String)(arg: T1) and applyDynamic[T1, T2](method: String)(arg1: T1, arg2: T2)")
|
|
}
|
|
}
|
|
|
|
// This has become noisy with implicit classes.
|
|
if (settings.isDeveloper && settings.warnPolyImplicitOverload) {
|
|
clazz.info.decls.foreach(sym => if (sym.isImplicit && sym.typeParams.nonEmpty) {
|
|
// implicit classes leave both a module symbol and a method symbol as residue
|
|
val alts = clazz.info.decl(sym.name).alternatives filterNot (_.isModule)
|
|
if (alts.size > 1)
|
|
alts foreach (x => refchecksWarning(x.pos, "parameterized overloaded implicit methods are not visible as view bounds", WarningCategory.LintPolyImplicitOverload))
|
|
})
|
|
}
|
|
}
|
|
|
|
// Override checking ------------------------------------------------------------
|
|
|
|
/** Add bridges for vararg methods that extend Java vararg methods
|
|
*/
|
|
def addVarargBridges(clazz: Symbol): List[Tree] = {
|
|
// This is quite expensive, so attempt to skip it completely.
|
|
// Insist there at least be a java-defined ancestor which
|
|
// defines a varargs method. TODO: Find a cheaper way to exclude.
|
|
if (inheritsJavaVarArgsMethod(clazz)) {
|
|
log("Found java varargs ancestor in " + clazz.fullLocationString + ".")
|
|
val self = clazz.thisType
|
|
val bridges = new ListBuffer[Tree]
|
|
|
|
def varargBridge(member: Symbol, bridgetpe: Type): Tree = {
|
|
log(s"Generating varargs bridge for ${member.fullLocationString} of type $bridgetpe")
|
|
|
|
val newFlags = (member.flags | VBRIDGE) & ~PRIVATE
|
|
val bridge = member.cloneSymbolImpl(clazz, newFlags) setPos clazz.pos
|
|
bridge.setInfo(bridgetpe.cloneInfo(bridge))
|
|
clazz.info.decls enter bridge
|
|
|
|
val params = bridge.paramss.head
|
|
val elemtp = params.last.tpe.typeArgs.head
|
|
val idents = params map Ident
|
|
val lastarg = gen.wildcardStar(gen.mkWrapVarargsArray(idents.last, elemtp))
|
|
val body = Apply(Select(This(clazz), member), idents.init :+ lastarg)
|
|
|
|
localTyper typed DefDef(bridge, body)
|
|
}
|
|
|
|
// For all concrete non-private members (but: see below) that have a (Scala) repeated
|
|
// parameter: compute the corresponding method type `jtpe` with a Java repeated parameter
|
|
// if a method with type `jtpe` exists and that method is not a varargs bridge
|
|
// then create a varargs bridge of type `jtpe` that forwards to the
|
|
// member method with the Scala vararg type.
|
|
//
|
|
// @PP: Can't call nonPrivateMembers because we will miss refinement members,
|
|
// which have been marked private. See scala/bug#4729.
|
|
for (member <- nonTrivialMembers(clazz)) {
|
|
log(s"Considering $member for java varargs bridge in $clazz")
|
|
if (!member.isDeferred && member.isMethod && hasRepeatedParam(member.info)) {
|
|
val inherited = clazz.info.nonPrivateMemberAdmitting(member.name, VBRIDGE)
|
|
|
|
// Delaying calling memberType as long as possible
|
|
if (inherited.exists) {
|
|
val jtpe = toJavaRepeatedParam(self memberType member)
|
|
// this is a bit tortuous: we look for non-private members or bridges
|
|
// if we find a bridge everything is OK. If we find another member,
|
|
// we need to create a bridge
|
|
val inherited1 = inherited filter (sym => !(sym hasFlag VBRIDGE) && (self memberType sym matches jtpe))
|
|
if (inherited1.exists)
|
|
bridges += varargBridge(member, jtpe)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bridges.size > 0)
|
|
log(s"Adding ${bridges.size} bridges for methods extending java varargs.")
|
|
|
|
bridges.toList
|
|
}
|
|
else Nil
|
|
}
|
|
|
|
/** 1. Check all members of class `clazz` for overriding conditions.
|
|
* That is for overriding member M and overridden member O:
|
|
*
|
|
* 1.1. M must have the same or stronger access privileges as O.
|
|
* 1.2. O must not be final.
|
|
* 1.3. O is deferred, or M has `override` modifier.
|
|
* 1.4. If O is stable, then so is M.
|
|
* 1.6. If O is a type alias, then M is an alias of O.
|
|
* 1.7. If O is an abstract type then
|
|
* 1.7.1 either M is an abstract type, and M's bounds are sharper than O's bounds.
|
|
* or M is a type alias or class which conforms to O's bounds.
|
|
* 1.7.2 higher-order type arguments must respect bounds on higher-order type parameters -- @M
|
|
* (explicit bounds and those implied by variance annotations) -- @see checkKindBounds
|
|
* 1.8. If O and M are values, then
|
|
* 1.8.1 M's type is a subtype of O's type, or
|
|
* 1.8.2 M is of type []S, O is of type ()T and S <: T, or
|
|
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
|
|
* 1.9. If M is a macro def, O cannot be deferred unless there's a concrete method overriding O.
|
|
* 1.10. If M is not a macro def, O cannot be a macro def.
|
|
* 2. Check that only abstract classes have deferred members
|
|
* 3. Check that concrete classes do not have deferred definitions
|
|
* that are not implemented in a subclass.
|
|
* 4. Check that every member with an `override` modifier
|
|
* overrides some other member.
|
|
* 5. Check that the nested class do not shadow other nested classes from outer class's parent.
|
|
*/
|
|
private def checkAllOverrides(clazz: Symbol, typesOnly: Boolean = false): Unit = {
|
|
val self = clazz.thisType
|
|
|
|
case class MixinOverrideError(member: Symbol, msg: String, actions: List[CodeAction], s3Migration: Boolean)
|
|
|
|
val mixinOverrideErrors = new ListBuffer[MixinOverrideError]()
|
|
|
|
def issue(pos: Position, msg: String, actions: List[CodeAction], s3Migration: Boolean) =
|
|
if (s3Migration) runReporting.warning(pos, msg, WarningCategory.Scala3Migration, currentOwner, actions)
|
|
else runReporting.error(pos, msg, actions)
|
|
|
|
def printMixinOverrideErrors(): Unit = {
|
|
mixinOverrideErrors.toList match {
|
|
case List() =>
|
|
case List(MixinOverrideError(_, msg, actions, s3Migration)) =>
|
|
issue(clazz.pos, msg, actions, s3Migration)
|
|
case MixinOverrideError(member, msg, actions, s3Migration) :: others =>
|
|
val others1 = others.map(_.member.name.decode).filter(member.name.decode != _).distinct
|
|
issue(
|
|
clazz.pos,
|
|
if (others1.isEmpty) msg
|
|
else s"$msg;\n other members with override errors are: ${others1.mkString(", ")}",
|
|
actions,
|
|
s3Migration)
|
|
}
|
|
}
|
|
|
|
def infoString(sym: Symbol) = infoString0(sym, sym.owner != clazz)
|
|
def infoStringWithLocation(sym: Symbol) = infoString0(sym, showLocation = true)
|
|
|
|
def infoString0(member: Symbol, showLocation: Boolean) = {
|
|
val location =
|
|
if (!showLocation) ""
|
|
else member.ownsString match {
|
|
case "" => ""
|
|
case s => s" (defined in $s)"
|
|
}
|
|
val macroStr = if (member.isTermMacro) "macro " else ""
|
|
|
|
macroStr + member.defStringSeenAs(self.memberInfo(member)) + location
|
|
}
|
|
|
|
/* Check that all conditions for overriding `other` by `member` of class `clazz` are met.
|
|
*
|
|
* TODO: error messages could really be improved, including how they are composed
|
|
*/
|
|
def checkOverride(pair: SymbolPair): Unit = {
|
|
import pair.{highType, lowType, highInfo, rootType}
|
|
|
|
val member = pair.low
|
|
val other = pair.high
|
|
val memberClass = member.owner
|
|
val otherClass = other.owner
|
|
|
|
// debuglog(s"Checking validity of ${member.fullLocationString} overriding ${other.fullLocationString}")
|
|
|
|
def noErrorType = !pair.isErroneous
|
|
def isRootOrNone(sym: Symbol) = sym != null && sym.isRoot || sym == NoSymbol
|
|
val isMemberClass = memberClass == clazz
|
|
def isNeitherInClass = !isMemberClass && otherClass != clazz
|
|
|
|
/** Emit an error if member is owned by current class, using the member position.
|
|
* Otherwise, accumulate the error, to be emitted after other messages, using the class position.
|
|
*/
|
|
def emitOverrideError(fullmsg: String, actions: List[CodeAction] = Nil, s3Migration: Boolean = false): Unit =
|
|
if (isMemberClass) issue(member.pos, fullmsg, actions, s3Migration)
|
|
else mixinOverrideErrors += MixinOverrideError(member, fullmsg, actions, s3Migration)
|
|
|
|
def overriddenWithAddendum(msg: String, foundReq: Boolean = settings.isDebug): String = {
|
|
val isConcreteOverAbstract =
|
|
otherClass.isSubClass(memberClass) && other.isDeferred && !member.isDeferred
|
|
val addendum =
|
|
if (isConcreteOverAbstract)
|
|
sm"""|;
|
|
|${indent}(note that ${infoStringWithLocation(other)} is abstract,
|
|
|${indent}and is therefore overridden by concrete ${infoStringWithLocation(member)})"""
|
|
else if (foundReq) {
|
|
def info(sym: Symbol) = self.memberInfo(sym) match { case tp if sym.isGetter || sym.isValue && !sym.isMethod => tp.resultType case tp => tp }
|
|
analyzer.foundReqMsg(info(member), info(other))
|
|
}
|
|
else ""
|
|
val msg1 = if (!msg.isEmpty) s"\n$indent$msg" else msg
|
|
|
|
s"${infoStringWithLocation(other)}${msg1}${addendum}"
|
|
}
|
|
|
|
def overrideError(msg: String): Unit =
|
|
if (noErrorType) emitOverrideError(msg)
|
|
|
|
def getWithIt = if (isMemberClass) "" else s"with ${infoString(member)}"
|
|
|
|
def overrideErrorWithMemberInfo(msg: String, actions: List[CodeAction] = Nil, s3Migration: Boolean = false): Unit =
|
|
if (noErrorType) emitOverrideError(s"${msg}\n${overriddenWithAddendum(getWithIt)}", actions, s3Migration)
|
|
|
|
def overrideErrorOrNullaryWarning(msg: String, actions: List[CodeAction]): Unit = if (isMemberClass || !member.owner.isSubClass(other.owner))
|
|
if (currentRun.isScala3)
|
|
overrideErrorWithMemberInfo(msg, actions, s3Migration = true)
|
|
else if (isMemberClass)
|
|
refchecksWarning(member.pos, msg, WarningCategory.OtherNullaryOverride, actions)
|
|
else
|
|
refchecksWarning(clazz.pos, msg, WarningCategory.OtherNullaryOverride, actions)
|
|
|
|
def overrideTypeError(): Unit =
|
|
if (member.isModule && other.isModule)
|
|
overrideError(sm"""|overriding ${other.fullLocationString} with ${member.fullLocationString}:
|
|
|an overriding object must conform to the overridden object's class bound${
|
|
analyzer.foundReqMsg(pair.lowClassBound, pair.highClassBound)}""")
|
|
else {
|
|
val needSameType = !other.isDeferred && other.isAliasType
|
|
val msg = s"${getWithIt}${if (needSameType) " (Equivalent type required when overriding a type alias.)" else ""}"
|
|
overrideError(sm"""|incompatible type in overriding
|
|
|${overriddenWithAddendum(msg, foundReq = !needSameType)}""")
|
|
}
|
|
|
|
def overrideErrorConcreteMissingOverride() =
|
|
if (isNeitherInClass && !otherClass.isSubClass(memberClass))
|
|
emitOverrideError(sm"""|$clazz inherits conflicting members:
|
|
|$indent${infoStringWithLocation(other)} and
|
|
|$indent${infoStringWithLocation(member)}
|
|
|$indent(note: this can be resolved by declaring an `override` in $clazz.)""")
|
|
else
|
|
overrideErrorWithMemberInfo("`override` modifier required to override concrete member:")
|
|
|
|
def weakerAccessError(advice: String): Unit =
|
|
overrideError(sm"""|weaker access privileges in overriding
|
|
|${overriddenWithAddendum(advice)}""")
|
|
def overrideAccessError(): Unit =
|
|
weakerAccessError {
|
|
accessFlagsToString(other) match {
|
|
case "" => "override should be public"
|
|
case otherAccess => s"override should at least be $otherAccess"
|
|
}
|
|
}
|
|
|
|
//Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG
|
|
|
|
/* Is the intersection between given two lists of overridden symbols empty? */
|
|
def intersectionIsEmpty(syms1: List[Symbol], syms2: List[Symbol]) = !syms1.exists(syms2.contains)
|
|
|
|
if (memberClass == ObjectClass && otherClass == AnyClass) () // skip -- can we have a mode of symbolpairs where this pair doesn't even appear?
|
|
else if (typesOnly) checkOverrideTypes()
|
|
else {
|
|
// o: public | protected | package-protected (aka java's default access)
|
|
// ^-may be overridden by member with access privileges-v
|
|
// m: public | public/protected | public/protected/package-protected-in-same-package-as-o
|
|
|
|
if (member.isPrivate) // (1.1)
|
|
weakerAccessError("override should not be private")
|
|
|
|
// todo: align accessibility implication checking with isAccessible in Contexts
|
|
@inline def protectedOK = !other.isProtected || member.isProtected
|
|
@inline def accessBoundaryOK = {
|
|
val ob = other.accessBoundary(memberClass)
|
|
val mb = member.accessBoundary(memberClass)
|
|
@inline def companionBoundaryOK = ob.isClass && mb.isModuleClass && mb.module == ob.companionSymbol
|
|
!isRootOrNone(ob) && (ob.hasTransOwner(mb) || companionBoundaryOK)
|
|
}
|
|
@inline def otherIsJavaProtected = other.isJavaDefined && other.isProtected
|
|
val isOverrideAccessOK =
|
|
member.isPublic || // member is public, definitely same or relaxed access
|
|
protectedOK && // if o is protected, so is m
|
|
(accessBoundaryOK || // m relaxes o's access boundary
|
|
otherIsJavaProtected // overriding a protected java member, see #3946 #12349
|
|
)
|
|
if (!isOverrideAccessOK)
|
|
overrideAccessError()
|
|
else if (other.isClass)
|
|
overrideErrorWithMemberInfo("class definitions cannot be overridden:")
|
|
else if (!other.isDeferred && member.isClass)
|
|
overrideErrorWithMemberInfo("classes can only override abstract types; cannot override:")
|
|
else if (other.isEffectivelyFinal) // (1.2)
|
|
overrideErrorWithMemberInfo("cannot override final member:")
|
|
else {
|
|
// In Java, the OVERRIDE flag is implied
|
|
val memberOverrides = member.isAnyOverride || (member.isJavaDefined && !member.isDeferred)
|
|
|
|
// Concrete `other` requires `override` for `member`.
|
|
// Synthetic exclusion for (at least) default getters, fixes scala/bug#5178.
|
|
// We cannot assign the OVERRIDE flag to the default getter:
|
|
// one default getter might sometimes override, sometimes not. Example in comment on ticket.
|
|
if (!memberOverrides && !other.isDeferred && !member.isSynthetic)
|
|
overrideErrorConcreteMissingOverride()
|
|
else if (other.isAbstractOverride && other.isIncompleteIn(clazz) && !member.isAbstractOverride)
|
|
overrideErrorWithMemberInfo("`abstract override` modifiers required to override:")
|
|
else if (memberOverrides && other.hasFlag(ACCESSOR) && !other.hasFlag(STABLE | DEFERRED))
|
|
// TODO: this is not covered by the spec.
|
|
overrideErrorWithMemberInfo("mutable variable cannot be overridden:")
|
|
else if (memberOverrides &&
|
|
!memberClass.thisType.baseClasses.exists(_.isSubClass(otherClass)) &&
|
|
!member.isDeferred && !other.isDeferred &&
|
|
intersectionIsEmpty(member.extendedOverriddenSymbols, other.extendedOverriddenSymbols))
|
|
overrideErrorWithMemberInfo("cannot override a concrete member without a third member that's overridden by both " +
|
|
"(this rule is designed to prevent accidental overrides)")
|
|
else if (other.isStable && !member.isStable) // (1.4)
|
|
overrideErrorWithMemberInfo("stable, immutable value required to override:")
|
|
else if (member.isValue && member.isLazy &&
|
|
other.isValue && other.hasStableFlag && !other.isDeferred && !other.isLazy)
|
|
overrideErrorWithMemberInfo("concrete non-lazy value cannot be overridden:")
|
|
else if (other.isValue && other.isLazy && member.isValue && !member.isLazy)
|
|
overrideErrorWithMemberInfo("value must be lazy when overriding concrete lazy value:")
|
|
else if (other.isDeferred && member.isTermMacro && member.extendedOverriddenSymbols.forall(_.isDeferred)) // (1.9)
|
|
overrideErrorWithMemberInfo("macro cannot override abstract method:")
|
|
else if (other.isTermMacro && !member.isTermMacro) // (1.10)
|
|
overrideErrorWithMemberInfo("macro can only be overridden by another macro:")
|
|
else {
|
|
checkOverrideTypes()
|
|
// Don't bother users with deprecations caused by classes they inherit.
|
|
// Only warn for the pair that has one leg in `clazz`.
|
|
if (isMemberClass) checkOverrideDeprecated()
|
|
def javaDetermined(sym: Symbol) = sym.isJavaDefined || isUniversalMember(sym)
|
|
def exempted = javaDetermined(member) || javaDetermined(other) || member.overrides.exists(javaDetermined)
|
|
// warn that nilary member matched nullary other, so either it was adapted by namer or will be silently mixed in by mixin
|
|
def warnAdaptedNullaryOverride(): Unit = {
|
|
val mbr = if (isMemberClass) "method" else s"${member.fullLocationString} defined"
|
|
val msg = s"$mbr without a parameter list overrides ${other.fullLocationString} defined with a single empty parameter list"
|
|
val namePos = member.pos
|
|
val action =
|
|
if (namePos.isDefined && namePos.source.sourceAt(namePos) == member.decodedName)
|
|
runReporting.codeAction("add empty parameter list", namePos.focusEnd, "()", msg)
|
|
else Nil
|
|
overrideErrorOrNullaryWarning(msg, action)
|
|
}
|
|
def warnExtraParens(): Unit = {
|
|
val mbr = if (isMemberClass) "method" else s"${member.fullLocationString} defined"
|
|
val msg = s"$mbr with a single empty parameter list overrides ${other.fullLocationString} defined without a parameter list"
|
|
val namePos = member.pos
|
|
val action =
|
|
if (namePos.isDefined && namePos.source.sourceAt(namePos) == member.decodedName)
|
|
runReporting.codeAction("remove empty parameter list", namePos.focusEnd.withEnd(namePos.end + 2), "", msg, expected = Some(("()", currentUnit)))
|
|
else Nil
|
|
overrideErrorOrNullaryWarning(msg, action)
|
|
}
|
|
if (member.hasAttachment[NullaryOverrideAdapted.type]) {
|
|
if (!exempted)
|
|
warnAdaptedNullaryOverride()
|
|
}
|
|
else if (member.paramLists.isEmpty) {
|
|
// Definitions that directly override get a parameter list and a `NullaryOverrideAdapted` attachment
|
|
// in Namers. Here we also warn when there's a mismatch between two mixed-in members.
|
|
if (!member.isStable && other.paramLists.nonEmpty && !exempted && !other.overrides.exists(javaDetermined))
|
|
warnAdaptedNullaryOverride()
|
|
}
|
|
else if (other.paramLists.isEmpty) {
|
|
if (!exempted && !member.hasAnnotation(BeanPropertyAttr) && !member.hasAnnotation(BooleanBeanPropertyAttr))
|
|
warnExtraParens()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def checkOverrideAlias(): Unit = {
|
|
// Important: first check the pair has the same kind, since the substitution
|
|
// carries high's type parameter's bounds over to low, so that
|
|
// type equality doesn't consider potentially different bounds on low/high's type params.
|
|
// In b781e25afe this went from using memberInfo to memberType (now lowType/highType), tested by neg/override.scala.
|
|
// TODO: was that the right fix? it seems type alias's RHS should be checked by looking at the symbol's info
|
|
if (pair.sameKind && lowType.substSym(member.typeParams, other.typeParams) =:= highType) ()
|
|
else overrideTypeError() // (1.6)
|
|
}
|
|
def checkOverrideAbstractType(): Unit = {
|
|
if (!(highInfo.bounds containsType lowType)) { // (1.7.1)
|
|
overrideTypeError(); // todo: do an explaintypes with bounds here
|
|
explainTypes(_.bounds containsType _, highInfo, lowType)
|
|
}
|
|
// check overriding (abstract type --> abstract type or abstract type --> concrete type member (a type alias))
|
|
// making an abstract type member concrete is like passing a type argument
|
|
typer.infer.checkKindBounds(other :: Nil, lowType :: Nil, rootType, memberClass) match { // (1.7.2)
|
|
case Nil =>
|
|
case kindErrors =>
|
|
reporter.error(member.pos,
|
|
"The kind of " + member.keyString+" " + member.varianceString + member.nameString+
|
|
" does not conform to the expected kind of " + other.defString + other.locationString + "." +
|
|
kindErrors.toList.mkString("\n", ", ", ""))
|
|
}
|
|
// check a type alias's RHS corresponds to its declaration
|
|
// this overlaps somewhat with validateVariance
|
|
if (member.isAliasType) {
|
|
typer.infer.checkKindBounds(member :: Nil, lowType.normalize :: Nil, rootType, memberClass) match {
|
|
case Nil =>
|
|
case kindErrors =>
|
|
reporter.error(member.pos,
|
|
"The kind of the right-hand side "+lowType.normalize+" of " + member.keyString+" "+
|
|
member.varianceString + member.nameString+ " does not conform to its expected kind."+
|
|
kindErrors.toList.mkString("\n", ", ", ""))
|
|
}
|
|
}
|
|
else if (member.isAbstractType && lowType.isVolatile && !highInfo.upperBound.isVolatile)
|
|
overrideErrorWithMemberInfo("volatile type member cannot override type member with non-volatile upper bound:")
|
|
}
|
|
def checkOverrideTerm(): Unit = {
|
|
member.cookJavaRawInfo() // #11584, #11840
|
|
other.cookJavaRawInfo() // #2454
|
|
if (!overridesTypeInPrefix(lowType, highType, rootType)) { // 8
|
|
overrideTypeError()
|
|
explainTypes(lowType, highType)
|
|
}
|
|
if (member.isStable && !highType.isVolatile) {
|
|
if (lowType.isVolatile)
|
|
overrideErrorWithMemberInfo("member with volatile type cannot override member with non-volatile type:")
|
|
else lowType.normalize.resultType match {
|
|
case rt: RefinedType if !(rt =:= highType) && notYetCheckedOrAdd(rt, pair.base) =>
|
|
// might mask some inconsistencies -- check overrides
|
|
val tsym = rt.typeSymbol
|
|
if (tsym.pos == NoPosition) tsym setPos member.pos
|
|
checkAllOverrides(tsym, typesOnly = true)
|
|
case _ =>
|
|
}
|
|
}
|
|
}
|
|
def checkOverrideTypes(): Unit = {
|
|
if (other.isAliasType) checkOverrideAlias()
|
|
else if (other.isAbstractType) checkOverrideAbstractType()
|
|
else if (other.isTerm) checkOverrideTerm()
|
|
}
|
|
|
|
def checkOverrideDeprecated(): Unit = {
|
|
if (other.hasDeprecatedOverridingAnnotation && !(member.hasDeprecatedOverridingAnnotation || member.ownerChain.exists(_.isDeprecated))) {
|
|
val version = other.deprecatedOverridingVersion.getOrElse("")
|
|
val since = if (version.isEmpty) version else s" (since $version)"
|
|
val message = other.deprecatedOverridingMessage map (msg => s": $msg") getOrElse ""
|
|
val report = s"overriding ${other.fullLocationString} is deprecated$since$message"
|
|
runReporting.deprecationWarning(member.pos, other, member, report, version)
|
|
}
|
|
}
|
|
}
|
|
|
|
val opc = new overridingPairs.PairsCursor(clazz)
|
|
while (opc.hasNext) {
|
|
if (!opc.high.isClass)
|
|
checkOverride(opc.currentPair)
|
|
|
|
opc.next()
|
|
}
|
|
printMixinOverrideErrors()
|
|
|
|
// Verifying a concrete class has nothing unimplemented.
|
|
if (clazz.isConcreteClass && !typesOnly) {
|
|
val abstractErrors = ListBuffer.empty[String]
|
|
def abstractErrorMessage = abstractErrors.mkString("\n")
|
|
|
|
def abstractClassError(msg: String, supplement: String = "", mustBeMixin: Boolean = false): Unit = {
|
|
def prelude =
|
|
if (clazz.isAnonymousClass || clazz.isModuleClass) "object creation impossible."
|
|
else if (mustBeMixin) s"$clazz needs to be a mixin."
|
|
else s"$clazz needs to be abstract."
|
|
|
|
if (abstractErrors.isEmpty) abstractErrors += prelude
|
|
abstractErrors += msg
|
|
if (!supplement.isEmpty) abstractErrors += supplement
|
|
}
|
|
|
|
def javaErasedOverridingSym(sym: Symbol): Symbol =
|
|
clazz.tpe.nonPrivateMemberAdmitting(sym.name, BRIDGE).filter(other =>
|
|
!other.isDeferred && other.isJavaDefined && !sym.enclClass.isSubClass(other.enclClass) && {
|
|
// #3622: erasure operates on uncurried types --
|
|
// note on passing sym in both cases: only sym.isType is relevant for uncurry.transformInfo
|
|
// !!! erasure.erasure(sym, uncurry.transformInfo(sym, tp)) gives erroneous or inaccessible type - check whether that's still the case!
|
|
def uncurryAndErase(tp: Type) = erasure.erasure(sym)(uncurry.transformInfo(sym, tp))
|
|
val tp1 = uncurryAndErase(clazz.thisType.memberType(sym))
|
|
val tp2 = uncurryAndErase(clazz.thisType.memberType(other))
|
|
exitingErasure(tp1 matches tp2)
|
|
})
|
|
|
|
def ignoreDeferred(member: Symbol) =
|
|
(member.isAbstractType && !member.isFBounded) || (
|
|
// the test requires exitingErasure so shouldn't be
|
|
// done if the compiler has no erasure phase available
|
|
member.isJavaDefined
|
|
&& (currentRun.erasurePhase == NoPhase || javaErasedOverridingSym(member) != NoSymbol)
|
|
)
|
|
|
|
// 2. Check that only abstract classes have deferred members
|
|
def checkNoAbstractMembers(): Unit = {
|
|
val NoError = null.asInstanceOf[String]
|
|
val EmptyDiagnostic = ""
|
|
def diagnose(member: Symbol, accessors: List[Symbol], nonPrivateMembers: Scope, fastDiagnostics: Boolean): String = {
|
|
val underlying = analyzer.underlyingSymbol(member) // TODO: don't use this method
|
|
|
|
// Give a specific error message for abstract vars based on why it fails:
|
|
// It could be unimplemented, have only one accessor, or be uninitialized.
|
|
val isMultiple = accessors.size > 1
|
|
|
|
if (accessors.exists(_.isSetter) || (member.isGetter && !isMultiple && member.setterIn(member.owner).exists)) {
|
|
if (member.isSetter && isMultiple) NoError // If both getter and setter are missing, squelch the setter error.
|
|
else if (member.isSetter) "an abstract var requires a setter in addition to the getter"
|
|
else if (member.isGetter && !isMultiple) "an abstract var requires a getter in addition to the setter"
|
|
else "variables need to be initialized to be defined"
|
|
}
|
|
else if (!fastDiagnostics && underlying.isMethod) {
|
|
// Highlight any member that nearly matches: same name and arity,
|
|
// but differs in one param or param list.
|
|
val abstractParamLists = underlying.paramLists
|
|
val matchingArity = nonPrivateMembers.reverseIterator.filter { m =>
|
|
!m.isDeferred &&
|
|
m.name == underlying.name &&
|
|
sameLength(m.paramLists, abstractParamLists) &&
|
|
sumSize(m.paramLists, 0) == sumSize(abstractParamLists, 0) &&
|
|
sameLength(m.tpe.typeParams, underlying.tpe.typeParams) &&
|
|
!(m.isJavaDefined && m.hasFlag(JAVA_DEFAULTMETHOD))
|
|
}.toList
|
|
matchingArity match {
|
|
// So far so good: only one candidate method
|
|
case concrete :: Nil =>
|
|
val concreteParamLists = concrete.paramLists
|
|
val aplIter = abstractParamLists.iterator.flatten
|
|
val cplIter = concreteParamLists.iterator.flatten
|
|
def mismatch(apl: Symbol, cpl: Symbol): Option[(Type, Type)] =
|
|
if (apl.tpe.asSeenFrom(clazz.tpe, underlying.owner) =:= cpl.tpe) None else Some(apl.tpe -> cpl.tpe)
|
|
def missingImplicit = abstractParamLists.zip(concreteParamLists).exists {
|
|
case (abss, konkrete) => abss.headOption.exists(_.isImplicit) && !konkrete.headOption.exists(_.isImplicit)
|
|
}
|
|
val mismatches = mapFilter2(aplIter, cplIter)(mismatch).take(2).toList
|
|
mismatches match {
|
|
// Only one mismatched parameter: say something useful.
|
|
case (pa, pc) :: Nil =>
|
|
val abstractSym = pa.typeSymbol
|
|
val concreteSym = pc.typeSymbol
|
|
def subclassMsg(c1: Symbol, c2: Symbol) =
|
|
s": ${c1.fullLocationString} is a subclass of ${c2.fullLocationString}, but method parameter types must match exactly."
|
|
def wrongSig = {
|
|
val m = concrete
|
|
fullyInitializeSymbol(m)
|
|
m.defStringSeenAs(clazz.tpe_*.memberType(m))
|
|
}
|
|
val addendum =
|
|
if (abstractSym == concreteSym) {
|
|
if (underlying.isJavaDefined && pa.typeArgs.isEmpty && abstractSym.typeParams.nonEmpty)
|
|
s". To implement this raw type, use ${rawToExistential(pa)}"
|
|
else if (pa.prefix =:= pc.prefix)
|
|
": their type parameters differ"
|
|
else
|
|
": their prefixes (i.e., enclosing instances) differ"
|
|
}
|
|
else if (abstractSym.isSubClass(concreteSym)) subclassMsg(abstractSym, concreteSym)
|
|
else if (concreteSym.isSubClass(abstractSym)) subclassMsg(concreteSym, abstractSym)
|
|
else s" in `$wrongSig`"
|
|
s"$pa does not match $pc$addendum"
|
|
case Nil if missingImplicit => "overriding member must declare implicit parameter list" // other overriding gotchas
|
|
case _ => EmptyDiagnostic
|
|
}
|
|
case _ => EmptyDiagnostic
|
|
}
|
|
}
|
|
else EmptyDiagnostic
|
|
}
|
|
def emitErrors(missing: List[Symbol], nonPrivateMembers: Scope): Unit = {
|
|
val fastDiagnostics = missing.lengthCompare(100) > 0
|
|
// Group missing members by the name of the underlying symbol, to consolidate getters and setters.
|
|
val byName = missing.groupBy(_.name.getterName)
|
|
// There may be 1 or more missing members declared in 1 or more parents.
|
|
// If a single parent, the message names it. Otherwise, missing members are grouped by declaring class.
|
|
val byOwner = missing.groupBy(_.owner).toList
|
|
val announceOwner = byOwner.size > 1
|
|
def membersStrings(members: List[Symbol]) = {
|
|
members.sortBy(_.name).flatMap { m =>
|
|
val accessors = byName.getOrElse(m.name.getterName, Nil)
|
|
val diagnostic = diagnose(m, accessors, nonPrivateMembers, fastDiagnostics)
|
|
if (diagnostic == NoError) Nil
|
|
else {
|
|
val s0a = infoString0(m, showLocation = false)
|
|
fullyInitializeSymbol(m)
|
|
val s0b = m.defString
|
|
val s1 = m.defStringSeenAs(clazz.tpe_*.memberType(m))
|
|
val implMsg = if (s1 != s0a) s"implements `$s0a`" else if (s1 != s0b) s"implements `$s0b`" else ""
|
|
val spacer = if (diagnostic.nonEmpty && implMsg.nonEmpty) "; " else ""
|
|
val comment = if (diagnostic.nonEmpty || implMsg.nonEmpty) s" // $implMsg$spacer$diagnostic" else ""
|
|
s"$s1 = ???$comment" :: Nil
|
|
}
|
|
}
|
|
}
|
|
var count = 0
|
|
def isMulti = count > 1
|
|
def helpfulListing =
|
|
byOwner.sortBy(_._1.name.toString).flatMap {
|
|
case (owner, members) =>
|
|
val ms = membersStrings(members) :+ ""
|
|
count += ms.size - 1
|
|
if (announceOwner) s"// Members declared in ${owner.fullName}" :: ms else ms
|
|
}.init.map(s => s" $s\n").mkString
|
|
val stubs = helpfulListing
|
|
def singleParent = if (byOwner.size == 1 && byOwner.head._1 != clazz) s" member${if (isMulti) "s" else ""} of ${byOwner.head._1}" else ""
|
|
val line0 =
|
|
if (isMulti) s"Missing implementations for ${count}${val p = singleParent ; if (p.isEmpty) " members" else p}."
|
|
else s"Missing implementation${val p = singleParent ; if (p.isEmpty) p else s" for$p"}:"
|
|
abstractClassError(line0, supplement = stubs)
|
|
}
|
|
def filtered[A](it: Iterator[A])(p: A => Boolean)(q: A => Boolean): (List[A], List[A]) = {
|
|
var ps, qs: List[A] = Nil
|
|
while (it.hasNext) {
|
|
val a = it.next()
|
|
if (p(a)) ps ::= a
|
|
else if (q(a)) qs ::= a
|
|
}
|
|
(ps, qs)
|
|
}
|
|
val nonPrivateMembers = clazz.info.nonPrivateMembersAdmitting(VBRIDGE)
|
|
// Avoid extra allocations with reverseIterator. Filter for abstract members of interest, and bad abstract override.
|
|
val (missing, abstractIncomplete): (List[Symbol], List[Symbol]) =
|
|
filtered(nonPrivateMembers.reverseIterator)(m => m.isDeferred & !ignoreDeferred(m))(m => m.isAbstractOverride && m.isIncompleteIn(clazz))
|
|
if (missing.nonEmpty) emitErrors(missing, nonPrivateMembers)
|
|
// Check the remainder for invalid absoverride.
|
|
for (member <- abstractIncomplete) {
|
|
val explanation = member.superSymbolIn(clazz) match {
|
|
case NoSymbol => ", but no concrete implementation could be found in a base class"
|
|
case other => " and overrides incomplete superclass member\n" + infoString(other)
|
|
}
|
|
abstractClassError(s"${infoString(member)} is marked `abstract` and `override`$explanation", mustBeMixin = true)
|
|
}
|
|
} // end checkNoAbstractMembers
|
|
|
|
// 3. Check that concrete classes do not have deferred definitions
|
|
// that are not implemented in a subclass.
|
|
// Note that this is not the same as (2); In a situation like
|
|
//
|
|
// class C { def m: Int = 0}
|
|
// class D extends C { def m: Int }
|
|
//
|
|
// (3) is violated but not (2).
|
|
def checkNoAbstractDecls(bc: Symbol): Unit = {
|
|
for (decl <- bc.info.decls) {
|
|
if (decl.isDeferred && !ignoreDeferred(decl)) {
|
|
val impl = decl.matchingSymbol(clazz.thisType, admit = VBRIDGE)
|
|
if (impl == NoSymbol || decl.owner.isSubClass(impl.owner))
|
|
abstractClassError(s"No implementation found in a subclass for deferred declaration\n" +
|
|
s"${infoString(decl)}${analyzer.abstractVarMessage(decl)}")
|
|
}
|
|
}
|
|
if (bc.superClass hasFlag ABSTRACT)
|
|
checkNoAbstractDecls(bc.superClass)
|
|
}
|
|
|
|
checkNoAbstractMembers()
|
|
if (abstractErrors.isEmpty)
|
|
checkNoAbstractDecls(clazz)
|
|
|
|
if (abstractErrors.nonEmpty)
|
|
reporter.error(clazz.pos, abstractErrorMessage)
|
|
}
|
|
else if (clazz.isTrait && !clazz.isSubClass(AnyValClass)) {
|
|
// For non-AnyVal classes, prevent abstract methods in interfaces that override
|
|
// final members in Object; see #4431
|
|
for (decl <- clazz.info.decls) {
|
|
// Have to use matchingSymbol, not a method involving overridden symbols,
|
|
// because the scala type system understands that an abstract method here does not
|
|
// override a concrete method in Object. The jvm, however, does not.
|
|
val overridden = decl.matchingSymbol(ObjectClass, ObjectTpe)
|
|
if (overridden.isFinal)
|
|
reporter.error(decl.pos, "trait cannot redefine final method from class AnyRef")
|
|
}
|
|
}
|
|
|
|
/* Returns whether there is a symbol declared in class `inclazz`
|
|
* (which must be different from `clazz`) whose name and type
|
|
* seen as a member of `class.thisType` matches `member`'s.
|
|
*/
|
|
def hasMatchingSym(inclazz: Symbol, member: Symbol): Boolean = {
|
|
val isVarargs = hasRepeatedParam(member.tpe)
|
|
lazy val varargsType = toJavaRepeatedParam(member.tpe)
|
|
|
|
def isSignatureMatch(sym: Symbol) = !sym.isTerm || {
|
|
val symtpe = clazz.thisType.memberType(sym)
|
|
member.tpe.matches(symtpe) || (isVarargs && varargsType.matches(symtpe))
|
|
}
|
|
/* The rules for accessing members which have an access boundary are more
|
|
* restrictive in java than scala. Since java has no concept of package nesting,
|
|
* a member with "default" (package-level) access can only be accessed by members
|
|
* in the exact same package. Example:
|
|
*
|
|
* package a.b;
|
|
* public class JavaClass { void foo() { } }
|
|
*
|
|
* The member foo() can be accessed only from members of package a.b, and not
|
|
* nested packages like a.b.c. In the analogous scala class:
|
|
*
|
|
* package a.b
|
|
* class ScalaClass { private[b] def foo() = () }
|
|
*
|
|
* The member IS accessible to classes in package a.b.c. The javaAccessCheck logic
|
|
* is restricting the set of matching signatures according to the above semantics.
|
|
*/
|
|
def javaAccessCheck(sym: Symbol) = (
|
|
!inclazz.isJavaDefined // not a java defined member
|
|
|| !sym.hasAccessBoundary // no access boundary
|
|
|| sym.isProtected // marked protected in java, thus accessible to subclasses
|
|
|| sym.privateWithin == member.enclosingPackageClass // exact package match
|
|
)
|
|
def classDecl = inclazz.info.nonPrivateDecl(member.name)
|
|
.orElse(inclazz.info.nonPrivateDecl(member.unexpandedName))
|
|
def matchingSyms = classDecl.filter(sym => isSignatureMatch(sym) && javaAccessCheck(sym))
|
|
|
|
(inclazz != clazz) && (matchingSyms != NoSymbol)
|
|
}
|
|
|
|
// 4. Check that every defined member with an `override` modifier overrides some other member.
|
|
for (member <- clazz.info.decls)
|
|
if (member.isAnyOverride && !clazz.thisType.baseClasses.exists(hasMatchingSym(_, member))) {
|
|
// for (bc <- clazz.info.baseClasses.tail) Console.println("" + bc + " has " + bc.info.decl(member.name) + ":" + bc.info.decl(member.name).tpe);//DEBUG
|
|
|
|
val nonMatching: List[Symbol] = clazz.info.member(member.name).alternatives.filterNot(_.owner == clazz).filterNot(_.isFinal)
|
|
def issueError(suffix: String) = reporter.error(member.pos, member.toString() + " overrides nothing" + suffix)
|
|
nonMatching match {
|
|
case Nil =>
|
|
issueError("")
|
|
case ms =>
|
|
val superSigs = ms.map(m => m.defStringSeenAs(clazz.tpe memberType m)).mkString("\n")
|
|
issueError(s".\nNote: the super classes of ${member.owner} contain the following, non final members named ${member.name}:\n${superSigs}")
|
|
}
|
|
member resetFlag (OVERRIDE | ABSOVERRIDE) // Any Override
|
|
}
|
|
|
|
// 5. Check that the nested class do not shadow other nested classes from outer class's parent
|
|
def checkNestedClassShadow(): Unit =
|
|
if (clazz.isNestedClass && !clazz.isModuleClass) {
|
|
val overridden = clazz.owner.ancestors
|
|
.map(a => clazz.matchingSymbol(a, clazz.owner.thisType))
|
|
.filter(c => c.exists && c.isClass)
|
|
overridden foreach { sym2 =>
|
|
def msg(what: String) = s"shadowing a nested class of a parent is $what but $clazz shadows $sym2 defined in ${sym2.owner}; rename the class to something else"
|
|
if (currentRun.isScala3) runReporting.warning(clazz.pos, msg("deprecated"), WarningCategory.Scala3Migration, clazz)
|
|
else runReporting.deprecationWarning(clazz.pos, clazz, currentOwner, msg("deprecated"), "2.13.2")
|
|
}
|
|
}
|
|
checkNestedClassShadow()
|
|
} // end checkAllOverrides
|
|
|
|
// Basetype Checking --------------------------------------------------------
|
|
|
|
/** <ol>
|
|
* <li> <!-- 1 -->
|
|
* Check that later type instances in the base-type sequence
|
|
* are subtypes of earlier type instances of the same mixin.
|
|
* </li>
|
|
* </ol>
|
|
*/
|
|
private def validateBaseTypes(clazz: Symbol): Unit = {
|
|
val seenParents = mutable.HashSet[Type]()
|
|
val seenTypes = Array.fill[List[Type]](clazz.info.baseTypeSeq.length)(Nil)
|
|
val warnCloneable = settings.warnCloneableObject && clazz.isModuleClass
|
|
|
|
/* validate all base types of a class in reverse linear order. */
|
|
def register(tp: Type): Unit = {
|
|
val baseClass = tp.typeSymbol
|
|
if (baseClass.isClass) {
|
|
if (!baseClass.isTrait && !baseClass.isJavaDefined && !currentRun.compiles(baseClass) && !separatelyCompiledScalaSuperclass.contains(baseClass))
|
|
separatelyCompiledScalaSuperclass.update(baseClass, ())
|
|
val index = clazz.info.baseTypeIndex(baseClass)
|
|
if (index >= 0) {
|
|
if (!seenTypes(index).exists(_ <:< tp))
|
|
seenTypes(index) = tp :: seenTypes(index).filterNot(tp <:< _)
|
|
}
|
|
}
|
|
if (warnCloneable && baseClass.eq(JavaCloneableClass))
|
|
reporter.warning(clazz.pos, s"$clazz should not extend Cloneable.")
|
|
val remaining = tp.parents.filterNot(seenParents)
|
|
seenParents ++= remaining
|
|
remaining.foreach(register)
|
|
}
|
|
register(clazz.tpe)
|
|
for (i <- 0 until seenTypes.length) {
|
|
val baseClass = clazz.info.baseTypeSeq(i).typeSymbol
|
|
seenTypes(i) match {
|
|
case Nil =>
|
|
devWarning(s"base $baseClass not found in basetypes of $clazz. This might indicate incorrect caching of TypeRef#parents.")
|
|
case _ :: Nil => // OK
|
|
case tp1 :: tp2 :: _ =>
|
|
reporter.error(clazz.pos,
|
|
sm"""|illegal inheritance;
|
|
| $clazz inherits different type instances of $baseClass:
|
|
|$tp1 and $tp2""")
|
|
explainTypes(tp1, tp2)
|
|
explainTypes(tp2, tp1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Variance Checking --------------------------------------------------------
|
|
|
|
object varianceValidator extends VarianceValidator {
|
|
private def tpString(tp: Type) = tp match {
|
|
case ClassInfoType(parents, _, clazz) => "supertype "+intersectionType(parents, clazz.owner)
|
|
case _ => "type "+tp
|
|
}
|
|
override def issueVarianceError(base: Symbol, sym: Symbol, required: Variance, tpe: Type): Unit = {
|
|
reporter.error(base.pos,
|
|
s"${sym.variance} $sym occurs in $required position in ${tpString(tpe)} of $base")
|
|
}
|
|
}
|
|
|
|
// Forward reference checking ---------------------------------------------------
|
|
|
|
class LevelInfo(val outer: LevelInfo) {
|
|
val scope: Scope = if (outer eq null) newScope else newNestedScope(outer.scope)
|
|
var maxindex: Int = Int.MinValue
|
|
var refpos: Position = _
|
|
var refsym: Symbol = _
|
|
}
|
|
|
|
private var currentLevel: LevelInfo = null
|
|
private val symIndex = perRunCaches.newMap[Symbol, Int]()
|
|
|
|
private def pushLevel(): Unit = {
|
|
currentLevel = new LevelInfo(currentLevel)
|
|
}
|
|
|
|
private def popLevel(): Unit = {
|
|
currentLevel = currentLevel.outer
|
|
}
|
|
|
|
private def enterSyms(stats: List[Tree]): Unit = {
|
|
var index = -1
|
|
for (stat <- stats) {
|
|
index = index + 1
|
|
|
|
stat match {
|
|
case _ : MemberDef if stat.symbol.isLocalToBlock =>
|
|
currentLevel.scope.enter(stat.symbol)
|
|
symIndex(stat.symbol) = index
|
|
case _ =>
|
|
}
|
|
}
|
|
}
|
|
|
|
private def enterReference(pos: Position, sym: Symbol): Unit = {
|
|
if (sym.isLocalToBlock) {
|
|
val e = currentLevel.scope.lookupEntry(sym.name)
|
|
if ((e ne null) && sym == e.sym) {
|
|
var l = currentLevel
|
|
while (l.scope != e.owner) l = l.outer
|
|
val symindex = symIndex(sym)
|
|
if (l.maxindex < symindex) {
|
|
l.refpos = pos
|
|
l.refsym = sym
|
|
l.maxindex = symindex
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comparison checking -------------------------------------------------------
|
|
object normalizeAll extends TypeMap {
|
|
def apply(tp: Type) = mapOver(tp).normalize
|
|
}
|
|
|
|
def checkImplicitViewOptionApply(pos: Position, fn: Tree, args: List[Tree]): Unit = if (settings.warnOptionImplicit) (fn, args) match {
|
|
case (tap@TypeApply(fun, targs), List(view: ApplyImplicitView)) if fun.symbol == currentRun.runDefinitions.Option_apply =>
|
|
refchecksWarning(pos, s"Suspicious application of an implicit view (${view.fun}) in the argument to Option.apply.", WarningCategory.LintOptionImplicit) // scala/bug#6567
|
|
case _ =>
|
|
}
|
|
|
|
private def isObjectOrAnyComparisonMethod(sym: Symbol) = sym match {
|
|
case Object_eq | Object_ne | Object_== | Object_!= | Any_== | Any_!= => true
|
|
case _ => false
|
|
}
|
|
|
|
/**
|
|
* Check the sensibility of using the given `equals` to compare `qual` and `other`.
|
|
*
|
|
* NOTE: I'm really not convinced by the logic here. I also think this would work better after erasure.
|
|
*/
|
|
private def checkSensibleEquals(pos: Position, qual: Tree, name: Name, sym: Symbol, other: Tree) = {
|
|
def isReferenceOp = sym == Object_eq || sym == Object_ne
|
|
def isNew(tree: Tree) = tree match {
|
|
case Function(_, _) | Apply(Select(New(_), nme.CONSTRUCTOR), _) => true
|
|
case _ => false
|
|
}
|
|
def underlyingClass(tp: Type): Symbol = {
|
|
val sym = tp.widen.typeSymbol
|
|
if (sym.isAbstractType) underlyingClass(sym.info.upperBound)
|
|
else sym
|
|
}
|
|
val actual = underlyingClass(other.tpe)
|
|
val receiver = underlyingClass(qual.tpe)
|
|
def onTrees[T](f: List[Tree] => T) = f(List(qual, other))
|
|
def onSyms[T](f: List[Symbol] => T) = f(List(receiver, actual))
|
|
|
|
// @MAT normalize for consistency in error message, otherwise only part is normalized due to use of `typeSymbol`
|
|
def typesString = s"${normalizeAll(qual.tpe.widen)} and ${normalizeAll(other.tpe.widen)}"
|
|
|
|
// TODO: this should probably be used in more type comparisons in checkSensibleEquals
|
|
def erasedClass(tp: Type) = erasure.javaErasure(tp).typeSymbol
|
|
|
|
/* Symbols which limit the warnings we can issue since they may be value types */
|
|
val couldBeAnything = Set[Symbol](ObjectClass, ComparableClass, SerializableClass)
|
|
def isMaybeValue(sym: Symbol): Boolean = couldBeAnything(erasedClass(sym.tpe))
|
|
|
|
// Whether def equals(other: Any) has known behavior: it is the default
|
|
// inherited from java.lang.Object, or it is a synthetically generated
|
|
// case equals. TODO - more cases are warnable if the target is a synthetic
|
|
// equals.
|
|
def isUsingWarnableEquals = {
|
|
val m = receiver.info.member(nme.equals_)
|
|
((m == Object_equals) || (m == Any_equals) || isMethodCaseEquals(m))
|
|
}
|
|
def isMethodCaseEquals(m: Symbol) = m.isSynthetic && m.owner.isCase
|
|
def isCaseEquals = isMethodCaseEquals(receiver.info.member(nme.equals_))
|
|
// Whether this == or != is one of those defined in Any/AnyRef or an overload from elsewhere.
|
|
def isUsingDefaultScalaOp = sym == Object_== || sym == Object_!= || sym == Any_== || sym == Any_!=
|
|
def haveSubclassRelationship = (actual isSubClass receiver) || (receiver isSubClass actual)
|
|
|
|
// Whether the operands+operator represent a warnable combo (assuming anyrefs)
|
|
// Looking for comparisons performed with ==/!= in combination with either an
|
|
// equals method inherited from Object or a case class synthetic equals (for
|
|
// which we know the logic.)
|
|
def isWarnable = isReferenceOp || (isUsingDefaultScalaOp && isUsingWarnableEquals)
|
|
def isEitherNullable = (NullTpe <:< receiver.info) || (NullTpe <:< actual.info)
|
|
def isEitherValueClass = actual.isDerivedValueClass || receiver.isDerivedValueClass
|
|
def isBoolean(s: Symbol) = unboxedValueClass(s) == BooleanClass
|
|
def isUnit(s: Symbol) = unboxedValueClass(s) == UnitClass
|
|
def isNumeric(s: Symbol) = isNumericValueClass(unboxedValueClass(s)) || isAnyNumber(s)
|
|
def isScalaNumber(s: Symbol) = s isSubClass ScalaNumberClass
|
|
def isJavaNumber(s: Symbol) = s isSubClass JavaNumberClass
|
|
// includes java.lang.Number if appropriate [scala/bug#5779]
|
|
def isAnyNumber(s: Symbol) = isScalaNumber(s) || isJavaNumber(s)
|
|
def isMaybeAnyValue(s: Symbol) = isPrimitiveValueClass(unboxedValueClass(s)) || isMaybeValue(s)
|
|
// used to short-circuit unrelatedTypes check if both sides are special
|
|
def isSpecial(s: Symbol) = isMaybeAnyValue(s) || isAnyNumber(s)
|
|
val nullCount = onSyms(_.filter(_ == NullClass).size)
|
|
def isNonsenseValueClassCompare = (
|
|
!haveSubclassRelationship
|
|
&& isUsingDefaultScalaOp
|
|
&& isEitherValueClass
|
|
&& !isCaseEquals
|
|
)
|
|
|
|
def isEffectivelyFinalDeep(sym: Symbol): Boolean = (
|
|
sym.isEffectivelyFinal
|
|
// If a parent of an intersection is final, the resulting type must effectively be final.
|
|
// (Any subclass of the refinement would have to be a subclass of that final parent.)
|
|
// OPT: this condition is not included in the standard isEffectivelyFinal check, as it's expensive
|
|
|| sym.isRefinementClass && sym.info.parents.exists { _.typeSymbol.isEffectivelyFinal }
|
|
)
|
|
|
|
// Have we already determined that the comparison is non-sensible? I mean, non-sensical?
|
|
var isNonSensible = false
|
|
|
|
def nonSensibleWarning(what: String, alwaysEqual: Boolean) = {
|
|
val msg = alwaysEqual == (name == nme.EQ || name == nme.eq)
|
|
refchecksWarning(pos, s"comparing $what using `${name.decode}` will always yield $msg", WarningCategory.Other)
|
|
isNonSensible = true
|
|
}
|
|
def nonSensible(pre: String, alwaysEqual: Boolean) =
|
|
nonSensibleWarning(s"${pre}values of types $typesString", alwaysEqual)
|
|
def nonSensiblyEq() = nonSensible("", alwaysEqual = true)
|
|
def nonSensiblyNeq() = nonSensible("", alwaysEqual = false)
|
|
def nonSensiblyNew() = nonSensibleWarning("a fresh object", alwaysEqual = false)
|
|
|
|
def unrelatedMsg = name match {
|
|
case nme.EQ | nme.eq => "never compare equal"
|
|
case _ => "always compare unequal"
|
|
}
|
|
def unrelatedTypes() = if (!isNonSensible) {
|
|
val weaselWord = if (isEitherValueClass) "" else " most likely"
|
|
refchecksWarning(pos, s"$typesString are unrelated: they will$weaselWord $unrelatedMsg", WarningCategory.Other)
|
|
}
|
|
|
|
if (nullCount == 2) // null == null
|
|
nonSensiblyEq()
|
|
else if (nullCount == 1) {
|
|
if (onSyms(_ exists isPrimitiveValueClass)) // null == 5
|
|
nonSensiblyNeq()
|
|
else if (onTrees( _ exists isNew)) // null == new AnyRef
|
|
nonSensiblyNew()
|
|
}
|
|
else if (isBoolean(receiver)) {
|
|
if (!isBoolean(actual) && !isMaybeValue(actual)) // true == 5
|
|
nonSensiblyNeq()
|
|
}
|
|
else if (isUnit(receiver)) {
|
|
if (isUnit(actual)) // () == ()
|
|
nonSensiblyEq()
|
|
else if (!isUnit(actual) && !isMaybeValue(actual)) // () == "abc"
|
|
nonSensiblyNeq()
|
|
}
|
|
else if (isNumeric(receiver)) {
|
|
if (!isNumeric(actual))
|
|
if (isUnit(actual) || isBoolean(actual) || !isMaybeValue(actual)) // 5 == "abc"
|
|
nonSensiblyNeq()
|
|
}
|
|
else if (isWarnable && !isCaseEquals) {
|
|
if (isNew(qual)) // new X == y
|
|
nonSensiblyNew()
|
|
else if (isNew(other) && (isEffectivelyFinalDeep(receiver) || isReferenceOp)) // object X ; X == new Y
|
|
nonSensiblyNew()
|
|
else if (isEffectivelyFinalDeep(actual) && isEffectivelyFinalDeep(receiver) && !haveSubclassRelationship) { // object X, Y; X == Y
|
|
if (isEitherNullable)
|
|
nonSensible("non-null ", alwaysEqual = false)
|
|
else
|
|
nonSensiblyNeq()
|
|
}
|
|
}
|
|
|
|
// warn if one but not the other is a derived value class
|
|
// this is especially important to enable transitioning from
|
|
// regular to value classes without silent failures.
|
|
if (isNonsenseValueClassCompare)
|
|
unrelatedTypes()
|
|
// possibleNumericCount is insufficient or this will warn on e.g. Boolean == j.l.Boolean
|
|
else if (isWarnable && nullCount == 0 && !(isSpecial(receiver) && isSpecial(actual))) {
|
|
// Warn if types are unrelated, without interesting lub. (Don't bother if we don't know anything about the values we're comparing.)
|
|
def warnIfLubless(): Unit = {
|
|
if (isMaybeValue(actual) || isMaybeValue(receiver) || haveSubclassRelationship) {} // ignore trivial or related types
|
|
else {
|
|
// better to have lubbed and lost
|
|
// We erase the lub because the erased type is closer to what happens at run time.
|
|
// Also, the lub of `S` and `String` is, weirdly, the refined type `Serializable{}` (for `class S extends Serializable`),
|
|
// which means we can't just take its type symbol and look it up in our isMaybeValue Set.
|
|
val commonRuntimeClass = erasedClass(global.lub(List(actual.tpe, receiver.tpe)))
|
|
if (commonRuntimeClass == ObjectClass)
|
|
unrelatedTypes()
|
|
}
|
|
}
|
|
|
|
// warn if actual has a case parent that is not same as receiver's;
|
|
// if actual is not a case, then warn if no common supertype, as below
|
|
if (isCaseEquals) {
|
|
def thisCase = receiver.info.member(nme.equals_).owner
|
|
actual.info.baseClasses.find(_.isCase) match {
|
|
case Some(p) if p != thisCase => nonSensible("case class ", alwaysEqual = false)
|
|
case None =>
|
|
// stronger message on (Some(1) == None)
|
|
//if (receiver.isCase && receiver.isEffectivelyFinal && !(receiver isSubClass actual)) nonSensiblyNeq()
|
|
//else
|
|
// if a class, it must be super to thisCase (and receiver) since not <: thisCase
|
|
if (!actual.isTrait && !(receiver isSubClass actual)) nonSensiblyNeq()
|
|
else warnIfLubless()
|
|
case _ =>
|
|
}
|
|
}
|
|
else warnIfLubless()
|
|
}
|
|
}
|
|
|
|
private def checkSensibleAnyEquals(pos: Position, qual: Tree, name: Name, sym: Symbol, other: Tree) = {
|
|
def underlyingClass(tp: Type): Symbol = {
|
|
val sym = tp.widen.typeSymbol
|
|
if (sym.isAbstractType) underlyingClass(sym.info.upperBound)
|
|
else sym
|
|
}
|
|
val receiver = underlyingClass(qual.tpe)
|
|
val actual = underlyingClass(other.tpe)
|
|
def typesString = "" + normalizeAll(qual.tpe.widen) + " and " + normalizeAll(other.tpe.widen)
|
|
def nonSensiblyEquals() = {
|
|
refchecksWarning(pos, s"comparing values of types $typesString using `${name.decode}` unsafely bypasses cooperative equality; use `==` instead", WarningCategory.OtherNonCooperativeEquals)
|
|
}
|
|
def isScalaNumber(s: Symbol) = s isSubClass ScalaNumberClass
|
|
def isJavaNumber(s: Symbol) = s isSubClass JavaNumberClass
|
|
def isAnyNumber(s: Symbol) = isScalaNumber(s) || isJavaNumber(s)
|
|
def isNumeric(s: Symbol) = isNumericValueClass(unboxedValueClass(s)) || isAnyNumber(s)
|
|
def isReference(s: Symbol) = (unboxedValueClass(s) isSubClass AnyRefClass) || (s isSubClass ObjectClass)
|
|
def isUnit(s: Symbol) = unboxedValueClass(s) == UnitClass
|
|
def isNumOrNonRef(s: Symbol) = isNumeric(s) || (!isReference(s) && !isUnit(s))
|
|
if (isNumeric(receiver) && isNumOrNonRef(actual)) {
|
|
if (receiver == actual) ()
|
|
else nonSensiblyEquals()
|
|
}
|
|
else if ((sym == Any_equals || sym == Object_equals) && isNumOrNonRef(actual) && !isReference(receiver)) {
|
|
nonSensiblyEquals()
|
|
}
|
|
}
|
|
|
|
/** Sensibility check examines flavors of equals. */
|
|
def checkSensible(pos: Position, fn: Tree, args: List[Tree]) = fn match {
|
|
case Select(qual, name @ (nme.EQ | nme.NE | nme.eq | nme.ne)) if args.length == 1 && isObjectOrAnyComparisonMethod(fn.symbol) && (!currentOwner.isSynthetic || currentOwner.isAnonymousFunction) =>
|
|
checkSensibleEquals(pos, qual, name, fn.symbol, args.head)
|
|
case Select(qual, name @ nme.equals_) if args.length == 1 && (!currentOwner.isSynthetic || currentOwner.isAnonymousFunction) =>
|
|
checkSensibleAnyEquals(pos, qual, name, fn.symbol, args.head)
|
|
case _ =>
|
|
}
|
|
|
|
// scala/bug#6276 warn for trivial recursion, such as `def foo = foo` or `val bar: X = bar`, which come up more frequently than you might think.
|
|
// TODO: Move to abide rule. Also, this does not check that the def is final or not overridden, for example
|
|
def checkInfiniteLoop(sym: Symbol, rhs: Tree): Unit =
|
|
if (!sym.isValueParameter && sym.paramss.forall(_.isEmpty)) {
|
|
rhs match {
|
|
case Ident(_) | Select(This(_), _) | Apply(Select(This(_), _), _) if rhs hasSymbolWhich (_.accessedOrSelf == sym) =>
|
|
refchecksWarning(rhs.pos, s"${sym.fullLocationString} does nothing other than call itself recursively", WarningCategory.Other)
|
|
case _ =>
|
|
}
|
|
}
|
|
|
|
// Transformation ------------------------------------------------------------
|
|
|
|
/* Convert a reference to a case factory of type `tpe` to a new of the class it produces. */
|
|
def toConstructor(pos: Position, tpe: Type): Tree = {
|
|
val rtpe = tpe.finalResultType
|
|
assert(rtpe.typeSymbol hasFlag CASE, tpe)
|
|
val tree = localTyper.typedOperator {
|
|
atPos(pos) {
|
|
Select(New(TypeTree(rtpe)), rtpe.typeSymbol.primaryConstructor)
|
|
}
|
|
}
|
|
checkUndesiredProperties(rtpe.typeSymbol, tree.pos)
|
|
checkUndesiredProperties(rtpe.typeSymbol.primaryConstructor, tree.pos)
|
|
tree
|
|
}
|
|
|
|
override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
|
|
pushLevel()
|
|
try {
|
|
enterSyms(stats)
|
|
var index = -1
|
|
stats.mapConserve(stat => {
|
|
index += 1;
|
|
transformStat(stat, index)
|
|
}).filter(_ ne EmptyTree)
|
|
}
|
|
finally popLevel()
|
|
}
|
|
|
|
private def showCurrentRef: String = {
|
|
val refsym = currentLevel.refsym
|
|
s"$refsym defined on line ${refsym.pos.line}"
|
|
}
|
|
|
|
def transformStat(tree: Tree, index: Int): Tree = tree match {
|
|
case t if treeInfo.isSelfConstrCall(t) =>
|
|
assert(index == 0, index)
|
|
try transform(tree)
|
|
finally if (currentLevel.maxindex > 0) {
|
|
// An implementation restriction to avoid VerifyErrors and lazy vals mishaps; see scala/bug#4717
|
|
reporter.error(currentLevel.refpos, s"forward reference to $showCurrentRef not allowed from self constructor invocation")
|
|
}
|
|
case ValDef(_, _, _, _) =>
|
|
val tree1 = transform(tree) // important to do before forward reference check
|
|
if (tree1.symbol.isLazy) tree1
|
|
else {
|
|
val sym = tree.symbol
|
|
if (sym.isLocalToBlock && index <= currentLevel.maxindex) {
|
|
reporter.error(currentLevel.refpos, s"forward reference to $showCurrentRef extends over definition of $sym")
|
|
}
|
|
tree1
|
|
}
|
|
case Import(_, _) => EmptyTree
|
|
case DefDef(mods, _, _, _, _, _) if (mods hasFlag MACRO) || (tree.symbol hasFlag MACRO) => EmptyTree
|
|
case _ => transform(tree)
|
|
}
|
|
|
|
/* Check whether argument types conform to bounds of type parameters */
|
|
private def checkBounds(tree0: Tree, pre: Type, owner: Symbol, tparams: List[Symbol], argtps: List[Type]): Unit =
|
|
try typer.infer.checkBounds(tree0, pre, owner, tparams, argtps, "")
|
|
catch {
|
|
case ex: TypeError =>
|
|
reporter.error(tree0.pos, ex.getMessage())
|
|
if (settings.explaintypes.value) {
|
|
val bounds = tparams map (tp => tp.info.instantiateTypeParams(tparams, argtps).bounds)
|
|
foreach2(argtps, bounds)((targ, bound) => explainTypes(bound.lo, targ))
|
|
foreach2(argtps, bounds)((targ, bound) => explainTypes(targ, bound.hi))
|
|
}
|
|
}
|
|
private def isIrrefutable(pat: Tree, seltpe: Type): Boolean = pat match {
|
|
case Apply(_, args) =>
|
|
val clazz = pat.tpe.typeSymbol
|
|
clazz == seltpe.typeSymbol &&
|
|
clazz.isCaseClass &&
|
|
(args corresponds clazz.primaryConstructor.tpe.asSeenFrom(seltpe, clazz).paramTypes)(isIrrefutable)
|
|
case Typed(pat, tpt) =>
|
|
seltpe <:< tpt.tpe
|
|
case Ident(tpnme.WILDCARD) =>
|
|
true
|
|
case Bind(_, pat) =>
|
|
isIrrefutable(pat, seltpe)
|
|
case _ =>
|
|
false
|
|
}
|
|
|
|
// Note: if a symbol has both @deprecated and @migration annotations and both
|
|
// warnings are enabled, only the first one checked here will be emitted.
|
|
// I assume that's a consequence of some code trying to avoid noise by suppressing
|
|
// warnings after the first, but I think it'd be better if we didn't have to
|
|
// arbitrarily choose one as more important than the other.
|
|
private def checkUndesiredProperties(sym: Symbol, pos: Position): Unit = {
|
|
// Issue a warning if symbol is deprecated, unless the point of reference is enclosed by a deprecated member,
|
|
// or has a deprecated companion.
|
|
if (sym.isDeprecated &&
|
|
// synthetic calls to deprecated case class constructor
|
|
!(sym.isConstructor && sym.owner.isCaseClass && currentOwner.isSynthetic) &&
|
|
!currentOwner.ownersIterator.exists(_.isDeprecated))
|
|
runReporting.deprecationWarning(pos, sym, currentOwner)
|
|
|
|
// Similar to deprecation: check if the symbol is marked with @migration
|
|
// indicating it has changed semantics between versions.
|
|
if (sym.hasMigrationAnnotation && settings.Xmigration.value != NoScalaVersion) {
|
|
val changed = try
|
|
settings.Xmigration.value < ScalaVersion(sym.migrationVersion.get)
|
|
catch {
|
|
case e : NumberFormatException =>
|
|
refchecksWarning(pos, s"${sym.fullLocationString} has an unparsable version number: ${e.getMessage()}", WarningCategory.Other)
|
|
// if we can't parse the format on the migration annotation just conservatively assume it changed
|
|
true
|
|
}
|
|
if (changed)
|
|
refchecksWarning(pos, s"${sym.fullLocationString} has changed semantics in version ${sym.migrationVersion.get}:\n${sym.migrationMessage.get}", WarningCategory.OtherMigration)
|
|
}
|
|
if (sym.isExperimental && !currentOwner.ownerChain.exists(x => x.isExperimental)) {
|
|
val msg =
|
|
s"${sym.fullLocationString} is marked @experimental and therefore its enclosing scope must be experimental."
|
|
reporter.error(pos, msg)
|
|
}
|
|
// See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly.
|
|
// async/await is expanded after erasure
|
|
if (sym.isCompileTimeOnly && !inAnnotation && !currentOwner.ownerChain.exists(x => x.isCompileTimeOnly)) {
|
|
if (!async.deferCompileTimeOnlyError(sym)) {
|
|
def defaultMsg =
|
|
sm"""Reference to ${sym.fullLocationString} should not have survived past type checking,
|
|
|it should have been processed and eliminated during expansion of an enclosing macro."""
|
|
// The getOrElse part should never happen, it's just here as a backstop.
|
|
val msg = sym.compileTimeOnlyMessage getOrElse defaultMsg
|
|
reporter.error(pos, msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
private def checkDelayedInitSelect(qual: Tree, sym: Symbol, pos: Position) = {
|
|
def isLikelyUninitialized = (
|
|
(sym.owner isSubClass DelayedInitClass)
|
|
&& !qual.tpe.isInstanceOf[ThisType]
|
|
&& sym.accessedOrSelf.isVal
|
|
)
|
|
if (settings.warnDelayedInit && isLikelyUninitialized)
|
|
refchecksWarning(pos, s"Selecting ${sym} from ${sym.owner}, which extends scala.DelayedInit, is likely to yield an uninitialized value", WarningCategory.LintDelayedinitSelect)
|
|
}
|
|
|
|
private def lessAccessible(otherSym: Symbol, memberSym: Symbol): Boolean = (
|
|
(otherSym != NoSymbol)
|
|
&& !otherSym.isProtected
|
|
&& !otherSym.isTypeParameterOrSkolem
|
|
&& !otherSym.isExistentiallyBound
|
|
&& memberSym.ownersIterator.forall(otherSym.isLessAccessibleThan(_))
|
|
)
|
|
private def lessAccessibleSymsInType(other: Type, memberSym: Symbol): List[Symbol] = {
|
|
val extras = other match {
|
|
case TypeRef(pre, _, args) =>
|
|
// checking the prefix here gives us spurious errors on e.g. a private[process]
|
|
// object which contains a type alias, which normalizes to a visible type.
|
|
args.filterNot(_ eq NoPrefix).flatMap(lessAccessibleSymsInType(_, memberSym))
|
|
case _ =>
|
|
Nil
|
|
}
|
|
if (lessAccessible(other.typeSymbol, memberSym)) other.typeSymbol :: extras
|
|
else extras
|
|
}
|
|
private def warnLessAccessible(otherSym: Symbol, memberSym: Symbol): Unit = {
|
|
val comparison = accessFlagsToString(memberSym) match {
|
|
case "" => ""
|
|
case acc => " is " + acc + " but"
|
|
}
|
|
val cannot =
|
|
if (memberSym.isDeferred) "may be unable to provide a concrete implementation of"
|
|
else "may be unable to override"
|
|
|
|
refchecksWarning(memberSym.pos,
|
|
s"""|${memberSym.fullLocationString}${comparison} references ${accessFlagsToString(otherSym)} ${otherSym}.
|
|
|Classes which cannot access ${otherSym.decodedName} ${cannot} ${memberSym.decodedName}.""".stripMargin,
|
|
WarningCategory.LintInaccessible
|
|
)
|
|
}
|
|
|
|
/** Warn about situations where a method signature will include a type which
|
|
* has more restrictive access than the method itself.
|
|
*/
|
|
private def checkAccessibilityOfReferencedTypes(tree: Tree): Unit = {
|
|
val member = tree.symbol
|
|
|
|
def checkAccessibilityOfType(tpe: Type): Unit = {
|
|
val inaccessible = lessAccessibleSymsInType(tpe, member)
|
|
// if the unnormalized type is accessible, that's good enough
|
|
if (inaccessible.isEmpty) ()
|
|
// or if the normalized type is, that's good too
|
|
else if ((tpe ne tpe.normalize) && lessAccessibleSymsInType(tpe.dealiasWiden, member).isEmpty) ()
|
|
// otherwise warn about the inaccessible syms in the unnormalized type
|
|
else inaccessible.foreach(warnLessAccessible(_, member))
|
|
}
|
|
|
|
// types of the value parameters
|
|
foreachParamss(member)(p => checkAccessibilityOfType(p.tpe))
|
|
// upper bounds of type parameters
|
|
member.typeParams.foreach(tp => checkAccessibilityOfType(tp.info.upperBound.widen))
|
|
}
|
|
|
|
/** Check that a deprecated val or def does not override a
|
|
* concrete, non-deprecated method. If it does, then
|
|
* deprecation is meaningless.
|
|
*/
|
|
private def checkDeprecatedOvers(tree: Tree): Unit = {
|
|
val symbol = tree.symbol
|
|
if (symbol.isDeprecated) {
|
|
val concrOvers =
|
|
symbol.allOverriddenSymbols.filter(sym =>
|
|
!sym.isDeprecated && !sym.isDeferred && !sym.hasDeprecatedOverridingAnnotation && !sym.enclClass.hasDeprecatedInheritanceAnnotation)
|
|
if(!concrOvers.isEmpty)
|
|
runReporting.deprecationWarning(
|
|
tree.pos,
|
|
symbol,
|
|
currentOwner,
|
|
s"${symbol.toString} overrides concrete, non-deprecated symbol(s): ${concrOvers.map(_.name.decode).mkString(", ")}", "")
|
|
}
|
|
}
|
|
private def checkRepeatedParamArg(tree: Tree): Unit = {
|
|
val bailure = "such annotations are only allowed in arguments to *-parameters"
|
|
val err = currentApplication match {
|
|
case Apply(fn, args) =>
|
|
val ok = ( args.nonEmpty
|
|
&& (args.last eq tree)
|
|
&& (fn.tpe.params.length == args.length)
|
|
&& isRepeatedParamType(fn.tpe.params.last.tpe)
|
|
)
|
|
if (ok) null
|
|
else if (!args.exists(tree.eq)) bailure
|
|
else {
|
|
val i = args.indexWhere(tree.eq)
|
|
val isLast = i == args.length - 1
|
|
val formal = if (i >= fn.tpe.params.length - 1) fn.tpe.params.last.tpe else fn.tpe.params(i).tpe
|
|
val isRepeated = isRepeatedParamType(formal)
|
|
val lastly = if (!isLast) ";\nsequence argument must be the last argument" else ""
|
|
val solely = if (fn.tpe.params.length == 1) "single" else "corresponding"
|
|
if (isRepeated)
|
|
s"it is not the only argument to be passed to the $solely repeated parameter $formal$lastly"
|
|
else
|
|
s"the $solely parameter has type $formal which is not a repeated parameter type$lastly"
|
|
}
|
|
case _ => bailure
|
|
}
|
|
if (err != null)
|
|
reporter.error(tree.pos, s"Sequence argument type annotation `: _*` cannot be used here:\n$err")
|
|
}
|
|
|
|
private object RefCheckTypeMap extends TypeMap {
|
|
object UnboundExistential extends TypeMap {
|
|
private[this] val bound = mutable.Set.empty[Symbol]
|
|
|
|
def toWildcardIn(tpe: Type): Type =
|
|
try apply(tpe) finally bound.clear()
|
|
|
|
override def apply(tpe: Type): Type = tpe match {
|
|
case ExistentialType(quantified, _) =>
|
|
bound ++= quantified
|
|
tpe.mapOver(this)
|
|
case tpe =>
|
|
val sym = tpe.typeSymbol
|
|
if (sym.isExistential && !bound(sym)) WildcardType
|
|
else tpe.mapOver(this)
|
|
}
|
|
}
|
|
|
|
private[this] var inPattern = false
|
|
private[this] var skipBounds = false
|
|
private[this] var tree: Tree = EmptyTree
|
|
|
|
def check(tpe: Type, tree: Tree, inPattern: Boolean = false): Type = {
|
|
this.inPattern = inPattern
|
|
this.tree = tree
|
|
try apply(tpe) finally {
|
|
this.inPattern = false
|
|
this.skipBounds = false
|
|
this.tree = EmptyTree
|
|
}
|
|
}
|
|
|
|
// check all bounds, except those that are existential type parameters
|
|
// or those within typed annotated with @uncheckedBounds
|
|
override def apply(tpe: Type): Type = tpe match {
|
|
case tpe: AnnotatedType if tpe.hasAnnotation(UncheckedBoundsClass) =>
|
|
// scala/bug#7694 Allow code synthesizers to disable checking of bounds for TypeTrees based on inferred LUBs
|
|
// which might not conform to the constraints.
|
|
val savedSkipBounds = skipBounds
|
|
skipBounds = true
|
|
try tpe.mapOver(this).filterAnnotations(_.symbol != UncheckedBoundsClass)
|
|
finally skipBounds = savedSkipBounds
|
|
case tpe: TypeRef =>
|
|
if (!inPattern) checkTypeRef(UnboundExistential.toWildcardIn(tpe))
|
|
checkUndesired(tpe.sym)
|
|
tpe.mapOver(this)
|
|
case tpe =>
|
|
tpe.mapOver(this)
|
|
}
|
|
|
|
private def checkTypeRef(tpe: Type): Unit = tpe match {
|
|
case TypeRef(pre, sym, args) =>
|
|
if (sym.isJavaDefined)
|
|
sym.typeParams.foreach(_.cookJavaRawInfo())
|
|
if (!tpe.isHigherKinded && !skipBounds)
|
|
checkBounds(tree, pre, sym.owner, sym.typeParams, args)
|
|
case _ =>
|
|
}
|
|
|
|
private def checkUndesired(sym: Symbol): Unit = tree match {
|
|
// scala/bug#7783 don't warn about inferred types
|
|
// FIXME: reconcile this check with one in resetAttrs
|
|
case tree: TypeTree if tree.original == null =>
|
|
case tree => checkUndesiredProperties(sym, tree.pos)
|
|
}
|
|
}
|
|
|
|
private def applyRefchecksToAnnotations(tree: Tree): Unit = {
|
|
def checkVarArgs(tp: Type, tree: Tree): Unit = tp match {
|
|
case TypeRef(_, VarargsClass, _) =>
|
|
tree match {
|
|
case tt: TypeTree if tt.original == null => // same exception as in checkTypeRef
|
|
case _: DefDef =>
|
|
case _ => reporter.error(tree.pos, s"Only methods can be marked @varargs")
|
|
}
|
|
case _ =>
|
|
}
|
|
def applyChecks(annots: List[AnnotationInfo]): List[AnnotationInfo] = if (annots.isEmpty) Nil else {
|
|
annots.foreach { ann =>
|
|
checkVarArgs(ann.atp, tree)
|
|
RefCheckTypeMap.check(ann.atp, tree)
|
|
if (ann.original != null && ann.original.hasExistingSymbol)
|
|
checkUndesiredProperties(ann.original.symbol, tree.pos)
|
|
}
|
|
val annotsBySymbol = new mutable.LinkedHashMap[Symbol, ListBuffer[AnnotationInfo]]()
|
|
val transformedAnnots = {
|
|
val saved = inAnnotation
|
|
inAnnotation = true
|
|
try annots.map(_.transformArgs(transformTrees)) finally inAnnotation = saved
|
|
}
|
|
for (transformedAnnot <- transformedAnnots) {
|
|
val buffer = annotsBySymbol.getOrElseUpdate(transformedAnnot.symbol, new ListBuffer)
|
|
buffer += transformedAnnot
|
|
}
|
|
annotsBySymbol.iterator.flatMap(x => groupRepeatableAnnotations(x._1, x._2.toList)).toList
|
|
}
|
|
|
|
// assumes non-empty `anns`
|
|
def groupRepeatableAnnotations(sym: Symbol, anns: List[AnnotationInfo]): List[AnnotationInfo] =
|
|
if (!sym.isJavaDefined) anns
|
|
else anns match {
|
|
case single :: Nil => anns
|
|
case multiple =>
|
|
sym.getAnnotation(AnnotationRepeatableAttr) match {
|
|
case Some(repeatable) =>
|
|
repeatable.assocs.collectFirst {
|
|
case (nme.value, LiteralAnnotArg(Constant(c: Type))) => c
|
|
} match {
|
|
case Some(container) =>
|
|
val assocs = List(
|
|
nme.value -> ArrayAnnotArg(multiple.map(NestedAnnotArg(_)).toArray)
|
|
)
|
|
AnnotationInfo(container, args = Nil, assocs = assocs) :: Nil
|
|
case None =>
|
|
devWarning(s"@Repeatable $sym had no containing class")
|
|
multiple
|
|
}
|
|
case None =>
|
|
reporter.error(tree.pos, s"$sym may not appear multiple times on ${tree.symbol}")
|
|
multiple
|
|
}
|
|
}
|
|
|
|
def checkIsElidable(sym: Symbol): Unit = if (sym ne null) sym.elisionLevel.foreach { level =>
|
|
if (!sym.isMethod || sym.isAccessor || sym.isLazy || sym.isDeferred) {
|
|
val rest = if (sym.isDeferred) " The annotation affects only the annotated method, not overriding methods in subclasses." else ""
|
|
reporter.error(sym.pos, s"${sym.name}: Only concrete methods can be marked @elidable.$rest")
|
|
}
|
|
}
|
|
checkIsElidable(tree.symbol)
|
|
|
|
def checkMember(sym: Symbol): Unit = {
|
|
sym.setAnnotations(applyChecks(sym.annotations))
|
|
|
|
// validate implicitNotFoundMessage and implicitAmbiguousMessage
|
|
if (settings.lintImplicitNotFound) {
|
|
def messageWarning(name: String)(warn: String) =
|
|
refchecksWarning(tree.pos, s"Invalid $name message for ${sym}${sym.locationString}:\n$warn", WarningCategory.LintImplicitNotFound)
|
|
analyzer.ImplicitNotFoundMsg.check(sym) foreach messageWarning("implicitNotFound")
|
|
analyzer.ImplicitAmbiguousMsg.check(sym) foreach messageWarning("implicitAmbiguous")
|
|
}
|
|
|
|
if (settings.warnSerialization && sym.isClass && sym.hasAnnotation(SerialVersionUIDAttr)) {
|
|
def warn(what: String) =
|
|
refchecksWarning(tree.pos, s"@SerialVersionUID has no effect on $what", WarningCategory.LintSerial)
|
|
|
|
if (sym.isTrait) warn("traits")
|
|
else if (!sym.isSerializable) warn("non-serializable classes")
|
|
}
|
|
if (!sym.isMethod && !sym.isConstructor)
|
|
checkNoThrows(sym.annotations)
|
|
}
|
|
def checkNoThrows(anns: List[AnnotationInfo]): Unit =
|
|
if (anns.exists(_.symbol == ThrowsClass))
|
|
reporter.error(tree.pos, s"`@throws` only allowed for methods and constructors")
|
|
|
|
tree match {
|
|
case m: MemberDef =>
|
|
checkMember(m.symbol)
|
|
case tpt@TypeTree() =>
|
|
if (tpt.original != null)
|
|
tpt.original.foreach {
|
|
case dc: TypeTreeWithDeferredRefCheck =>
|
|
applyRefchecksToAnnotations(dc.check()) // #2416
|
|
case _ =>
|
|
}
|
|
if (!inPattern)
|
|
tree.setType(tree.tpe.map {
|
|
case AnnotatedType(anns, ul) =>
|
|
checkNoThrows(anns)
|
|
AnnotatedType(applyChecks(anns), ul)
|
|
case tp => tp
|
|
})
|
|
case _ =>
|
|
}
|
|
}
|
|
|
|
private def isSimpleCaseApply(tree: Tree): Boolean = {
|
|
val sym = tree.symbol
|
|
def isClassTypeAccessible(tree: Tree): Boolean = tree match {
|
|
case TypeApply(fun, targs) =>
|
|
isClassTypeAccessible(fun)
|
|
case Select(module, apply) =>
|
|
// scala/bug#4859 `CaseClass1().InnerCaseClass2()` must not be rewritten to `new InnerCaseClass2()`;
|
|
// {expr; Outer}.Inner() must not be rewritten to `new Outer.Inner()`.
|
|
treeInfo.isQualifierSafeToElide(module) &&
|
|
// scala/bug#5626 Classes in refinement types cannot be constructed with `new`.
|
|
!module.exists { case t @ Select(_, _) => t.symbol != null && t.symbol.isStructuralRefinementMember case _ => false }
|
|
case x => throw new MatchError(x)
|
|
}
|
|
sym.name == nme.apply &&
|
|
!sym.hasStableFlag && // ???
|
|
sym.isCase &&
|
|
isClassTypeAccessible(tree) &&
|
|
!tree.tpe.finalResultType.typeSymbol.primaryConstructor.isLessAccessibleThan(tree.symbol)
|
|
}
|
|
|
|
private def transformCaseApply(tree: Tree) = {
|
|
def loop(t: Tree): Unit = t match {
|
|
case Ident(_) =>
|
|
checkUndesiredProperties(t.symbol, t.pos)
|
|
case Select(qual, _) =>
|
|
checkUndesiredProperties(t.symbol, t.pos)
|
|
loop(qual)
|
|
case _ =>
|
|
}
|
|
|
|
tree foreach {
|
|
case i@Ident(_) =>
|
|
enterReference(i.pos, i.symbol) // scala/bug#5390 need to `enterReference` for `a` in `a.B()`
|
|
case _ =>
|
|
}
|
|
loop(tree)
|
|
toConstructor(tree.pos, tree.tpe)
|
|
}
|
|
|
|
private def transformApply(tree: Apply): Tree = tree match {
|
|
case Apply(
|
|
Select(qual, nme.withFilter),
|
|
List(Function(
|
|
List(ValDef(_, pname, tpt, _)),
|
|
Match(_, CaseDef(pat1, _, _) :: _))))
|
|
if ((pname startsWith nme.CHECK_IF_REFUTABLE_STRING) &&
|
|
isIrrefutable(pat1, tpt.tpe) && (qual.tpe <:< tree.tpe)) =>
|
|
|
|
transform(qual)
|
|
case Apply(fn, args) =>
|
|
// sensicality should be subsumed by the unreachability/exhaustivity/irrefutability
|
|
// analyses in the pattern matcher
|
|
if (!inPattern) {
|
|
checkImplicitViewOptionApply(tree.pos, fn, args)
|
|
checkSensible(tree.pos, fn, args) // TODO: this should move to preEraseApply, as reasoning about runtime semantics makes more sense in the JVM type system
|
|
checkNamedBooleanArgs(fn, args)
|
|
}
|
|
currentApplication = tree
|
|
tree
|
|
}
|
|
|
|
/** Check that boolean literals are passed as named args.
|
|
* The rule is enforced when the type of the parameter is `Boolean`.
|
|
* The rule is relaxed when the method has exactly one boolean parameter
|
|
* and it is the first parameter, such as `assert(false, msg)`.
|
|
*/
|
|
private def checkNamedBooleanArgs(fn: Tree, args: List[Tree]): Unit = {
|
|
val sym = fn.symbol
|
|
def applyDepth: Int = {
|
|
def loop(t: Tree, d: Int): Int =
|
|
t match {
|
|
case Apply(f, _) => loop(f, d+1)
|
|
case _ => d
|
|
}
|
|
loop(fn, 0)
|
|
}
|
|
def isAssertParadigm(params: List[Symbol]): Boolean = !sym.isConstructor && !sym.isCaseApplyOrUnapply && {
|
|
params match {
|
|
case h :: t => h.tpe == BooleanTpe && !t.exists(_.tpe == BooleanTpe)
|
|
case _ => false
|
|
}
|
|
}
|
|
if (settings.lintNamedBooleans && !sym.isJavaDefined && !args.isEmpty) {
|
|
val params = sym.paramLists(applyDepth)
|
|
if (!isAssertParadigm(params))
|
|
foreach2(args, params)((arg, param) => arg match {
|
|
case Literal(Constant(_: Boolean))
|
|
if arg.hasAttachment[UnnamedArg.type] && param.tpe.typeSymbol == BooleanClass && !param.deprecatedParamName.contains(nme.NO_NAME) =>
|
|
runReporting.warning(arg.pos, s"Boolean literals should be passed using named argument syntax for parameter ${param.name}.", WarningCategory.LintNamedBooleans, sym)
|
|
case _ =>
|
|
})
|
|
}
|
|
}
|
|
|
|
private def transformSelect(tree: Select): Tree = {
|
|
val Select(qual, name) = tree
|
|
val sym = tree.symbol
|
|
|
|
checkUndesiredProperties(sym, tree.pos)
|
|
checkDelayedInitSelect(qual, sym, tree.pos)
|
|
|
|
if (!sym.exists)
|
|
devWarning("Select node has NoSymbol! " + tree + " / " + tree.tpe)
|
|
|
|
if (name == nme.synchronized_ && isBoxedValueClass(qual.tpe.typeSymbol))
|
|
refchecksWarning(tree.pos, s"Suspicious `synchronized` call involving boxed primitive `${qual.tpe.typeSymbol.name}`", WarningCategory.LintUniversalMethods)
|
|
|
|
def checkSuper(mix: Name) =
|
|
// term should have been eliminated by super accessors
|
|
assert(!(qual.symbol.isTrait && sym.isTerm && mix == tpnme.EMPTY), (qual.symbol, sym, mix))
|
|
|
|
// Rewrite eligible calls to monomorphic case companion apply methods to the equivalent constructor call.
|
|
//
|
|
// Note: for generic case classes the rewrite needs to be handled at the enclosing `TypeApply` to transform
|
|
// `TypeApply(Select(C, apply), targs)` to `Select(New(C[targs]), <init>)`. In case such a `TypeApply`
|
|
// was deemed ineligible for transformation (e.g. the case constructor was private), the refchecks transform
|
|
// will recurse to this point with `Select(C, apply)`, which will have a type `[T](...)C[T]`.
|
|
//
|
|
// We don't need to perform the check on the Select node, and `!isHigherKinded will guard against this
|
|
// redundant (and previously buggy, scala/bug#9546) consideration.
|
|
if (!tree.tpe.isHigherKinded && isSimpleCaseApply(tree)) {
|
|
transformCaseApply(tree)
|
|
} else {
|
|
qual match {
|
|
case Super(_, mix) => checkSuper(mix)
|
|
case _ =>
|
|
}
|
|
tree
|
|
}
|
|
}
|
|
private def transformIf(tree: If): Tree = {
|
|
val If(cond, thenpart, elsepart) = tree
|
|
def unitIfEmpty(t: Tree): Tree =
|
|
if (t == EmptyTree) Literal(Constant(())).setPos(tree.pos).setType(UnitTpe) else t
|
|
|
|
cond.tpe match {
|
|
case FoldableConstantType(value) =>
|
|
val res = if (value.booleanValue) thenpart else elsepart
|
|
unitIfEmpty(res)
|
|
case _ => tree
|
|
}
|
|
}
|
|
|
|
// Warning about nullary methods returning Unit.
|
|
private def checkNullaryMethodReturnType(sym: Symbol) = sym.tpe match {
|
|
case NullaryMethodType(restpe) if restpe.typeSymbol == UnitClass =>
|
|
// this may be the implementation of e.g. a generic method being parameterized
|
|
// on Unit, in which case we had better let it slide.
|
|
val isOk = (
|
|
sym.isGetter
|
|
|| sym.isDefaultGetter
|
|
|| sym.allOverriddenSymbols.exists(over => !(over.tpe.resultType =:= sym.tpe.resultType))
|
|
|| sym.isArtifact
|
|
)
|
|
if (!isOk) {
|
|
val msg = s"side-effecting nullary methods are discouraged: suggest defining as `def ${sym.name.decode}()` instead"
|
|
val namePos = sym.pos.focus.withEnd(sym.pos.point + sym.decodedName.length)
|
|
val action =
|
|
if (namePos.source.sourceAt(namePos) == sym.decodedName)
|
|
runReporting.codeAction("add empty parameter list", namePos.focusEnd, "()", msg)
|
|
else Nil
|
|
refchecksWarning(sym.pos, msg, WarningCategory.LintNullaryUnit, action)
|
|
}
|
|
case _ => ()
|
|
}
|
|
|
|
// Verify classes extending AnyVal meet the requirements
|
|
private def checkAnyValSubclass(clazz: Symbol) =
|
|
if (clazz.isDerivedValueClass) {
|
|
if (clazz.isTrait)
|
|
reporter.error(clazz.pos, "Only classes (not traits) are allowed to extend AnyVal")
|
|
else if (clazz.hasAbstractFlag)
|
|
reporter.error(clazz.pos, "`abstract` modifier cannot be used with value classes")
|
|
}
|
|
|
|
private def checkUnexpandedMacro(t: Tree) =
|
|
if (!t.isDef && t.hasSymbolField && t.symbol.isTermMacro)
|
|
reporter.error(t.pos, "macro has not been expanded")
|
|
|
|
// if expression in statement position (of template or block)
|
|
// looks like a useful value that should not be ignored, warn and return true
|
|
// User specifies that an expression is boring by ascribing `e: Unit`.
|
|
// The subtree `e` will bear an attachment, but may be wrapped in adaptations.
|
|
private def checkInterestingResultInStatement(t: Tree): Boolean = {
|
|
def isUninterestingSymbol(sym: Symbol): Boolean =
|
|
sym != null && (
|
|
sym.isConstructor ||
|
|
sym.hasPackageFlag ||
|
|
sym.isPackageObjectOrClass ||
|
|
sym == BoxedUnitClass ||
|
|
sym == AnyClass ||
|
|
sym == AnyRefClass ||
|
|
sym == AnyValClass
|
|
)
|
|
def isUninterestingType(tpe: Type): Boolean =
|
|
tpe != null && (
|
|
isUnitType(tpe) ||
|
|
tpe.typeSymbol.isBottomClass ||
|
|
tpe =:= UnitTpe ||
|
|
tpe =:= BoxedUnitTpe ||
|
|
isTrivialTopType(tpe)
|
|
)
|
|
// java lacks this.type idiom to distinguish side-effecting method, so ignore result of invoking java method.
|
|
def isJavaApplication(t: Tree): Boolean = t match {
|
|
case Apply(f, _) => f.symbol.isJavaDefined && !isUniversalMember(f.symbol)
|
|
case _ => false
|
|
}
|
|
// The quirk of typechecking if is that the LUB often results in boring types.
|
|
// Parser adds suppressing attachment on `if (b) expr` when user has `-Wnonunit-if:false`.
|
|
def checkInterestingShapes(t: Tree): Boolean =
|
|
t match {
|
|
case If(_, thenpart, elsepart) => checkInterestingShapes(thenpart) || checkInterestingShapes(elsepart) // either or
|
|
//case Block(_, Apply(label, Nil)) if label.symbol != null && nme.isLoopHeaderLabel(label.symbol.name) => false
|
|
case Block(_, res) => checkInterestingShapes(res)
|
|
case Match(_, cases) => cases.exists(k => checkInterestingShapes(k.body))
|
|
case _ => checksForInterestingResult(t)
|
|
}
|
|
// tests for various flavors of blandness in expressions.
|
|
def checksForInterestingResult(t: Tree): Boolean = (
|
|
!t.isDef && !treeInfo.isPureDef(t) // ignore defs
|
|
&& !isUninterestingSymbol(t.symbol) // ctors, package, Unit, Any
|
|
&& !isUninterestingType(t.tpe) // bottom types, Unit, Any
|
|
&& !treeInfo.isThisTypeResult(t) // buf += x
|
|
&& !treeInfo.isSuperConstrCall(t) // just a thing
|
|
&& !treeInfo.hasExplicitUnit(t) // suppressed by explicit expr: Unit
|
|
&& !isJavaApplication(t) // Java methods are inherently side-effecting
|
|
)
|
|
// begin checkInterestingResultInStatement
|
|
settings.warnNonUnitStatement.value && checkInterestingShapes(t) && {
|
|
val where = t match {
|
|
case Block(_, res) => res
|
|
case If(_, thenpart, Literal(Constant(()))) =>
|
|
thenpart match {
|
|
case Block(_, res) => res
|
|
case _ => thenpart
|
|
}
|
|
case _ => t
|
|
}
|
|
def msg = s"unused value of type ${where.tpe} (add `: Unit` to discard silently)"
|
|
refchecksWarning(where.pos, msg, WarningCategory.OtherPureStatement)
|
|
true
|
|
}
|
|
} // end checkInterestingResultInStatement
|
|
|
|
override def transform(tree: Tree): Tree = {
|
|
val savedLocalTyper = localTyper
|
|
val savedCurrentApplication = currentApplication
|
|
try {
|
|
val sym = tree.symbol
|
|
|
|
// Apply RefChecks to annotations. Makes sure the annotations conform to
|
|
// type bounds (bug #935), issues deprecation warnings for symbols used
|
|
// inside annotations.
|
|
applyRefchecksToAnnotations(tree)
|
|
val result: Tree = tree match {
|
|
// NOTE: a val in a trait is now a DefDef, with the RHS being moved to an Assign in Constructors
|
|
case tree: ValOrDefDef =>
|
|
checkDeprecatedOvers(tree)
|
|
if (!tree.isErroneous)
|
|
checkInfiniteLoop(tree.symbol, tree.rhs)
|
|
|
|
if (settings.warnNullaryUnit)
|
|
checkNullaryMethodReturnType(sym)
|
|
if (settings.warnInaccessible) {
|
|
if (!sym.isConstructor && !sym.isEffectivelyFinalOrNotOverridden && !sym.owner.isSealed && !sym.isSynthetic)
|
|
checkAccessibilityOfReferencedTypes(tree)
|
|
}
|
|
tree match {
|
|
case dd: DefDef if sym.hasAnnotation(NativeAttr) =>
|
|
if (sym.owner.isTrait) {
|
|
reporter.error(tree.pos, "A trait cannot define a native method.")
|
|
tree
|
|
} else if (dd.rhs == EmptyTree) {
|
|
// pretend it had a stub implementation
|
|
sym resetFlag DEFERRED
|
|
deriveDefDef(dd)(_ => typed(gen.mkThrowNewRuntimeException("native method stub")))
|
|
} else
|
|
tree
|
|
case _ => tree
|
|
}
|
|
|
|
case Template(parents, self, body) =>
|
|
localTyper = localTyper.atOwner(tree, currentOwner)
|
|
for (stat <- body)
|
|
if (!checkInterestingResultInStatement(stat) && treeInfo.isPureExprForWarningPurposes(stat)) {
|
|
val msg = "a pure expression does nothing in statement position"
|
|
val clause = if (body.lengthCompare(1) > 0) "; multiline expressions may require enclosing parentheses" else ""
|
|
refchecksWarning(stat.pos, s"$msg$clause", WarningCategory.OtherPureStatement)
|
|
}
|
|
validateBaseTypes(currentOwner)
|
|
checkOverloadedRestrictions(currentOwner, currentOwner)
|
|
// scala/bug#7870 default getters for constructors live in the companion module
|
|
checkOverloadedRestrictions(currentOwner, currentOwner.companionModule)
|
|
val bridges = addVarargBridges(currentOwner) // TODO: do this during uncurry?
|
|
checkAllOverrides(currentOwner)
|
|
checkAnyValSubclass(currentOwner)
|
|
if (currentOwner.isDerivedValueClass)
|
|
currentOwner.primaryConstructor makeNotPrivate NoSymbol // scala/bug#6601, must be done *after* pickler!
|
|
if (bridges.nonEmpty) deriveTemplate(tree)(_ ::: bridges) else tree
|
|
|
|
case _: TypeTreeWithDeferredRefCheck => abort("adapt should have turned dc: TypeTreeWithDeferredRefCheck into tpt: TypeTree, with tpt.original == dc")
|
|
case tpt@TypeTree() =>
|
|
if(tpt.original != null) {
|
|
tpt.original foreach {
|
|
case dc: TypeTreeWithDeferredRefCheck =>
|
|
transform(dc.check()) // #2416 -- only call transform to do refchecks, but discard results
|
|
// tpt has the right type if the deferred checks are ok
|
|
case _ =>
|
|
}
|
|
}
|
|
|
|
tree.setType(RefCheckTypeMap.check(tree.tpe, tree, inPattern))
|
|
|
|
case TypeApply(fn, args) =>
|
|
checkBounds(tree, NoPrefix, NoSymbol, fn.tpe.typeParams, args map (_.tpe))
|
|
if (isSimpleCaseApply(tree))
|
|
transformCaseApply(tree)
|
|
else
|
|
tree
|
|
|
|
case x @ Apply(_, _) =>
|
|
transformApply(x)
|
|
|
|
case x @ If(_, _, _) =>
|
|
transformIf(x)
|
|
|
|
case New(tpt) =>
|
|
enterReference(tree.pos, tpt.tpe.typeSymbol)
|
|
tree
|
|
|
|
case treeInfo.WildcardStarArg(_) =>
|
|
checkRepeatedParamArg(tree)
|
|
tree
|
|
|
|
case Ident(name) =>
|
|
checkUndesiredProperties(sym, tree.pos)
|
|
if (name != nme.WILDCARD && name != tpnme.WILDCARD_STAR) {
|
|
assert(sym != NoSymbol, "transformCaseApply: name = " + name.debugString + " tree = " + tree + " / " + tree.getClass) //debug
|
|
enterReference(tree.pos, sym)
|
|
}
|
|
tree
|
|
|
|
case x @ Select(_, _) =>
|
|
transformSelect(x)
|
|
|
|
case Literal(Constant(tpe: Type)) =>
|
|
RefCheckTypeMap.check(tpe, tree)
|
|
tree
|
|
|
|
case UnApply(fun, args) =>
|
|
transform(fun) // just make sure we enterReference for unapply symbols, note that super.transform(tree) would not transform(fun)
|
|
// transformTrees(args) // TODO: is this necessary? could there be forward references in the args??
|
|
// probably not, until we allow parameterised extractors
|
|
tree
|
|
|
|
case blk @ Block(stats, expr) =>
|
|
// diagnostic info
|
|
val (count, result0, adapted) =
|
|
expr match {
|
|
case Block(expr :: Nil, Literal(Constant(()))) => (1, expr, true)
|
|
case Literal(Constant(())) => (0, EmptyTree, false)
|
|
case _ => (1, EmptyTree, false)
|
|
}
|
|
val isMultiline = stats.lengthCompare(1 - count) > 0
|
|
|
|
def checkPure(t: Tree, supple: Boolean): Unit =
|
|
if (!treeInfo.hasExplicitUnit(t) && treeInfo.isPureExprForWarningPurposes(t)) {
|
|
val msg = "a pure expression does nothing in statement position"
|
|
val parens = if (isMultiline) "multiline expressions might require enclosing parentheses" else ""
|
|
val discard = if (adapted) "; a value can be silently discarded when Unit is expected" else ""
|
|
val text =
|
|
if (supple) s"$parens$discard"
|
|
else if (!parens.isEmpty) s"$msg; $parens" else msg
|
|
refchecksWarning(t.pos, text, WarningCategory.OtherPureStatement)
|
|
}
|
|
// check block for unintended "expression in statement position"
|
|
stats.foreach { t => if (!checkInterestingResultInStatement(t)) checkPure(t, supple = false) }
|
|
if (result0.nonEmpty) checkPure(result0, supple = true)
|
|
|
|
def checkImplicitlyAdaptedBlockResult(t: Tree): Unit =
|
|
expr match {
|
|
case treeInfo.Applied(f, _, _) if f.symbol != null && f.symbol.isImplicit =>
|
|
f.symbol.paramLists match {
|
|
case (p :: Nil) :: _ if p.isByNameParam => refchecksWarning(t.pos, s"Block result was adapted via implicit conversion (${f.symbol}) taking a by-name parameter", WarningCategory.LintBynameImplicit)
|
|
case _ =>
|
|
}
|
|
case _ =>
|
|
}
|
|
if (isMultiline && settings.warnByNameImplicit) checkImplicitlyAdaptedBlockResult(expr)
|
|
|
|
tree
|
|
case Match(selector, cases) =>
|
|
// only warn if it could be put in backticks in a pattern
|
|
def isWarnable(sym: Symbol): Boolean =
|
|
sym != null && sym.exists &&
|
|
!sym.hasPackageFlag && sym.isStable && !isByName(sym) &&
|
|
!sym.hasAttachment[PatVarDefAttachment.type] // val (_, v) = with one var is shadowed in desugaring
|
|
//!toCheck.isSynthetic // self-type symbols are synthetic: value self (<synthetic>), do warn
|
|
|
|
class CheckSelector extends InternalTraverser {
|
|
var selectorSymbols: List[Symbol] = null
|
|
override def traverse(t: Tree): Unit = {
|
|
val include = t match {
|
|
case _: This => true // !t.symbol.isStable
|
|
case _: SymTree => isWarnable(t.symbol)
|
|
case _ => false
|
|
}
|
|
if (include) selectorSymbols ::= t.symbol
|
|
t.traverse(this)
|
|
}
|
|
// true if the shadowed toCheck appears in the selector expression
|
|
def implicatesSelector(toCheck: Symbol): Boolean = {
|
|
if (selectorSymbols == null) {
|
|
selectorSymbols = Nil
|
|
apply(selector)
|
|
}
|
|
selectorSymbols.exists(sym => sym.eq(toCheck) || sym.accessedOrSelf.eq(toCheck.accessedOrSelf) ||
|
|
toCheck.isThisSym && toCheck.owner == sym) // self match { case self: S => }, selector C.this is class symbol
|
|
}
|
|
}
|
|
val checkSelector = new CheckSelector
|
|
// true to warn about shadowed when selSym is the scrutinee
|
|
def checkShadowed(shadowed: Symbol): Boolean = {
|
|
def checkShadowedSymbol(toCheck: Symbol): Boolean =
|
|
isWarnable(toCheck) && !checkSelector.implicatesSelector(toCheck)
|
|
|
|
if (shadowed.isOverloaded) shadowed.alternatives.exists(checkShadowedSymbol)
|
|
else checkShadowedSymbol(shadowed)
|
|
}
|
|
// warn if any checkable pattern var shadows, in the context of the selector,
|
|
// or for `tree match case Apply(fun, args) =>` check whether names in args equal names of fun.params
|
|
def checkPattern(p: Tree): Unit = {
|
|
val traverser = new InternalTraverser {
|
|
// names absolved of shadowing because it is a "current" parameter (of a case class, etc)
|
|
var absolved: List[Name] = Nil
|
|
override def traverse(t: Tree): Unit = t match {
|
|
case Apply(_, args) =>
|
|
treeInfo.dissectApplied(t).core.tpe match {
|
|
case MethodType(ps, _) =>
|
|
foreach2(ps, args) { (p, arg) =>
|
|
absolved ::= p.name
|
|
try traverse(arg)
|
|
finally absolved = absolved.tail
|
|
}
|
|
case _ => t.traverse(this)
|
|
}
|
|
case bind @ Bind(name, _) =>
|
|
def richLocation(sym: Symbol): String = sym.ownsString match {
|
|
case "" => val n = sym.pos.line; if (n > 0) s"$sym at line $n" else sym.fullLocationString
|
|
case owns => s"$sym in $owns"
|
|
}
|
|
for (shade <- bind.getAndRemoveAttachment[PatShadowAttachment]) {
|
|
val shadowed = shade.shadowed
|
|
if (!absolved.contains(name) && !bind.symbol.hasTransOwner(shadowed.accessedOrSelf) && checkShadowed(shadowed))
|
|
refchecksWarning(bind.pos, s"Name $name is already introduced in an enclosing scope as ${richLocation(shadowed)}. Did you intend to match it using backquoted `$name`?", WarningCategory.OtherShadowing)
|
|
|
|
}
|
|
case _ => t.traverse(this)
|
|
}
|
|
}
|
|
traverser(p)
|
|
}
|
|
// check the patterns for unfriendly shadowing, patvars bearing PatShadowAttachment
|
|
if (settings.warnPatternShadow) for (cdef <- cases) checkPattern(cdef.pat)
|
|
tree
|
|
case _ => tree
|
|
}
|
|
|
|
// skip refchecks in patterns....
|
|
val result1 = result match {
|
|
case CaseDef(pat, guard, body) =>
|
|
val pat1 = savingInPattern {
|
|
inPattern = true
|
|
transform(pat)
|
|
}
|
|
treeCopy.CaseDef(tree, pat1, transform(guard), transform(body))
|
|
case _ =>
|
|
result.transform(this)
|
|
}
|
|
result1 match {
|
|
case ClassDef(_, _, _, _) | TypeDef(_, _, _, _) | ModuleDef(_, _, _) =>
|
|
if (result1.symbol.isLocalToBlock || result1.symbol.isTopLevel)
|
|
varianceValidator.traverse(result1)
|
|
case tt @ TypeTree() if tt.original != null =>
|
|
varianceValidator.validateVarianceOfPolyTypesIn(tt.tpe)
|
|
case _ =>
|
|
}
|
|
|
|
checkUnexpandedMacro(result1)
|
|
|
|
result1
|
|
} catch {
|
|
case ex: TypeError =>
|
|
if (settings.isDebug) ex.printStackTrace()
|
|
reporter.error(tree.pos, ex.getMessage())
|
|
tree
|
|
} finally {
|
|
localTyper = savedLocalTyper
|
|
currentApplication = savedCurrentApplication
|
|
}
|
|
}
|
|
}
|
|
}
|