diff --git a/examples/RefChecks.scala b/examples/RefChecks.scala
new file mode 100644
index 000000000..22bfdc69e
--- /dev/null
+++ b/examples/RefChecks.scala
@@ -0,0 +1,2158 @@
+/*
+ * 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 --------------------------------------------------------
+
+ /**
+ * -
+ * Check that later type instances in the base-type sequence
+ * are subtypes of earlier type instances of the same mixin.
+ *
+ *
+ */
+ 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]), )`. 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 (), 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
+ }
+ }
+ }
+}
diff --git a/grammar.js b/grammar.js
index 148c881ea..6b83e8bf3 100644
--- a/grammar.js
+++ b/grammar.js
@@ -1484,8 +1484,11 @@ module.exports = grammar({
operator_identifier: $ =>
token(
choice(
- // single opchar
- /[\-!#%&*+\/\\:<=>?@\u005e\u007c~\p{Sm}\p{So}]/,
+ // opchar minus colon, equal, at
+ // Technically speaking, Sm (Math symbols https://www.compart.com/en/unicode/category/Sm)
+ // should be allowed as a single-characeter opchar, however, it includes `=`,
+ // so we should to avoid that to prevent bad parsing of `=` as infix term or type.
+ /[\-!#%&*+\/\\<>?\u005e\u007c~\u00ac\u00b1\u00d7\u00f7\u2190-\u2194\p{So}]/,
seq(
// opchar minus slash
/[\-!#%&*+\\:<=>?@\u005e\u007c~\p{Sm}\p{So}]/,
diff --git a/script/smoke_test.sh b/script/smoke_test.sh
index 56705a818..466ff56f2 100755
--- a/script/smoke_test.sh
+++ b/script/smoke_test.sh
@@ -3,7 +3,7 @@
# This is an integration test to generally check the quality of parsing.
SCALA_SCALA_LIBRARY_EXPECTED=100
-SCALA_SCALA_COMPILER_EXPECTED=96
+SCALA_SCALA_COMPILER_EXPECTED=97
DOTTY_COMPILER_EXPECTED=85
SYNTAX_COMPLEXITY_CEILING=1400