From 91529e91572612c2021e96482e81a63e834a37b5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 14 Apr 2024 13:59:04 -0400 Subject: [PATCH 1/2] Exclude colon, at, and equal from single opchar **Problem** Currently operator_identifier includes characters like colon at and equal even though they cannot be a legal operator without backticks. Having equal etc pushes tree-sitter into thinking some construct to be an infix operation when they are `=`. Another compilication is Unicode Math symbols, which includes equal sign. **Solution** Remove colon, at, equal sign, and Math symbols from the single-char operator_identifier. This adds back back a few Math symbol unicodes. --- grammar.js | 7 +++++-- script/smoke_test.sh | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) 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 From 16a70759404f2ab7c50401e0e19a735ef36dfab0 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 14 Apr 2024 14:36:39 -0400 Subject: [PATCH 2/2] Add RefChecks.scala as differential test --- examples/RefChecks.scala | 2158 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2158 insertions(+) create mode 100644 examples/RefChecks.scala 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 -------------------------------------------------------- + + /**
    + *
  1. + * Check that later type instances in the base-type sequence + * are subtypes of earlier type instances of the same mixin. + *
  2. + *
+ */ + 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 + } + } + } +}