diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 9b130e7d7804..3dfaff13f608 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -7,7 +7,6 @@ import typer.{TyperPhase, RefChecks} import parsing.Parser import Phases.Phase import transform.* -import dotty.tools.backend import backend.jvm.{CollectSuperCalls, GenBCode} import localopt.StringInterpolatorOpt @@ -34,8 +33,7 @@ class Compiler { protected def frontendPhases: List[List[Phase]] = List(new Parser) :: // Compiler frontend: scanner, parser List(new TyperPhase) :: // Compiler frontend: namer, typer - List(new CheckUnused.PostTyper) :: // Check for unused elements - List(new CheckShadowing) :: // Check shadowing elements + List(new CheckUnused.PostTyper, new CheckShadowing) :: // Check for unused, shadowed elements List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB.ExtractSemanticInfo) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index cb730efbfe89..d714d3a04439 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3293,14 +3293,13 @@ extends Message(UnusedSymbolID) { override def explain(using Context) = "" } -object UnusedSymbol { - def imports(using Context): UnusedSymbol = new UnusedSymbol(i"unused import") - def localDefs(using Context): UnusedSymbol = new UnusedSymbol(i"unused local definition") - def explicitParams(using Context): UnusedSymbol = new UnusedSymbol(i"unused explicit parameter") - def implicitParams(using Context): UnusedSymbol = new UnusedSymbol(i"unused implicit parameter") - def privateMembers(using Context): UnusedSymbol = new UnusedSymbol(i"unused private member") - def patVars(using Context): UnusedSymbol = new UnusedSymbol(i"unused pattern variable") -} +object UnusedSymbol: + def imports(using Context): UnusedSymbol = UnusedSymbol(i"unused import") + def localDefs(using Context): UnusedSymbol = UnusedSymbol(i"unused local definition") + def explicitParams(using Context): UnusedSymbol = UnusedSymbol(i"unused explicit parameter") + def implicitParams(using Context): UnusedSymbol = UnusedSymbol(i"unused implicit parameter") + def privateMembers(using Context): UnusedSymbol = UnusedSymbol(i"unused private member") + def patVars(using Context): UnusedSymbol = UnusedSymbol(i"unused pattern variable") class NonNamedArgumentInJavaAnnotation(using Context) extends SyntaxMsg(NonNamedArgumentInJavaAnnotationID): diff --git a/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala b/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala index 87d652bd9133..795296874ad6 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala @@ -49,26 +49,22 @@ class CheckShadowing extends MiniPhase: override def description: String = CheckShadowing.description + override def isEnabled(using Context): Boolean = ctx.settings.Wshadow.value.nonEmpty + override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.Wshadow.value.nonEmpty && - !ctx.isJava + super.isRunnable && ctx.settings.Wshadow.value.nonEmpty && !ctx.isJava - // Setup before the traversal override def prepareForUnit(tree: tpd.Tree)(using Context): Context = val data = ShadowingData() val fresh = ctx.fresh.setProperty(_key, data) shadowingDataApply(sd => sd.registerRootImports())(using fresh) - // Reporting on traversal's end override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = shadowingDataApply(sd => reportShadowing(sd.getShadowingResult) ) tree - // MiniPhase traversal : - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = shadowingDataApply(sd => sd.inNewScope()) ctx @@ -94,14 +90,14 @@ class CheckShadowing extends MiniPhase: if tree.symbol.isAliasType then // if alias, the parent is the current symbol nestedTypeTraverser(tree.symbol).traverse(tree.rhs) if tree.symbol.is(Param) then // if param, the parent is up - val owner = tree.symbol.owner - val parent = if (owner.isConstructor) then owner.owner else owner - nestedTypeTraverser(parent).traverse(tree.rhs)(using ctx.outer) - shadowingDataApply(sd => sd.registerCandidate(parent, tree)) + val enclosing = + val owner = tree.symbol.ownersIterator.dropWhile(_.is(Param)).next() + if owner.isConstructor then owner.owner else owner + nestedTypeTraverser(enclosing).traverse(tree.rhs)(using ctx.outer) + shadowingDataApply(_.registerCandidate(enclosing, tree)) else ctx - override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = shadowingDataApply(sd => sd.outOfScope()) tree @@ -115,13 +111,16 @@ class CheckShadowing extends MiniPhase: tree override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - if tree.symbol.is(Param) && isValidTypeParamOwner(tree.symbol.owner) then // Do not register for constructors the work is done for the Class owned equivalent TypeDef - shadowingDataApply(sd => sd.computeTypeParamShadowsFor(tree.symbol.owner)(using ctx.outer)) - if tree.symbol.isAliasType then // No need to start outer here, because the TypeDef reached here it's already the parent + // Do not register for constructors the work is done for the Class owned equivalent TypeDef + if tree.symbol.is(Param) then + val owner = tree.symbol.ownersIterator.dropWhile(_.is(Param)).next() + if isValidTypeParamOwner(owner) then + shadowingDataApply(_.computeTypeParamShadowsFor(owner)(using ctx.outer)) + // No need to start outer here, because the TypeDef reached here it's already the parent + if tree.symbol.isAliasType then shadowingDataApply(sd => sd.computeTypeParamShadowsFor(tree.symbol)(using ctx)) tree - // Helpers : private def isValidTypeParamOwner(owner: Symbol)(using Context): Boolean = !owner.isConstructor && !owner.is(Synthetic) && !owner.is(Exported) @@ -310,4 +309,3 @@ object CheckShadowing: case class ShadowResult(warnings: List[ShadowWarning]) end CheckShadowing - diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index d647d50560d3..2979828171a0 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,314 +1,262 @@ package dotty.tools.dotc.transform -import scala.annotation.tailrec +import scala.annotation.* import dotty.tools.uncheckedNN -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.ast.tpd.{Inlined, TreeTraverser} -import dotty.tools.dotc.ast.untpd -import dotty.tools.dotc.ast.untpd.ImportSelector +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.ast.untpd, untpd.ImportSelector import dotty.tools.dotc.config.ScalaSettings import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Decorators.{em, i} +import dotty.tools.dotc.core.Decorators.{em, i, toMessage} import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.StdNames -import dotty.tools.dotc.report -import dotty.tools.dotc.reporting.Message -import dotty.tools.dotc.reporting.UnusedSymbol as UnusedSymbolMessage -import dotty.tools.dotc.typer.ImportInfo -import dotty.tools.dotc.util.{Property, SrcPos} -import dotty.tools.dotc.core.Mode -import dotty.tools.dotc.core.Types.{AnnotatedType, ConstantType, NoType, TermRef, Type, TypeTraverser} -import dotty.tools.dotc.core.Flags.flagsString +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Types.{AnnotatedType, ClassInfo, ConstantType, NamedType, NoType, TermRef, Type, TypeProxy, TypeTraverser} import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Names.{Name, TermName, termName} import dotty.tools.dotc.core.NameOps.isReplWrapperName -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.core.Annotations -import dotty.tools.dotc.core.Definitions import dotty.tools.dotc.core.NameKinds.WildcardParamName -import dotty.tools.dotc.core.Symbols.Symbol -import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol, defn, isDeprecated} +import dotty.tools.dotc.report +import dotty.tools.dotc.reporting.{Message, UnusedSymbol as UnusedSymbolMessage} +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.typer.ImportInfo +import dotty.tools.dotc.util.{Property, SrcPos} import dotty.tools.dotc.util.Spans.Span -import scala.math.Ordering - +import scala.util.chaining.given -/** - * A compiler phase that checks for unused imports or definitions +/** A compiler phase that checks for unused imports or definitions. * - * Basically, it gathers definition/imports and their usage. If a - * definition/imports does not have any usage, then it is reported. + * Every construct that introduces a name must have at least one corresponding reference. + * The analysis is restricted to definitions of limited scope, i.e., private and local definitions. */ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _key: Property.Key[CheckUnused.UnusedData]) extends MiniPhase: import CheckUnused.* import UnusedData.* - private inline def unusedDataApply[U](inline f: UnusedData => U)(using Context): Context = + private inline def ud(using ud: UnusedData): UnusedData = ud + + private inline def preparing[U](inline op: UnusedData ?=> U)(using ctx: Context): ctx.type = ctx.property(_key) match - case Some(ud) => f(ud) - case None => () + case Some(ud) => op(using ud) + case None => ctx override def phaseName: String = CheckUnused.phaseNamePrefix + suffix override def description: String = CheckUnused.description - override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.WunusedHas.any && - !ctx.isJava + override def isEnabled(using Context): Boolean = ctx.settings.WunusedHas.any - // ========== SETUP ============ + override def isRunnable(using Context): Boolean = super.isRunnable && ctx.settings.WunusedHas.any && !ctx.isJava - override def prepareForUnit(tree: tpd.Tree)(using Context): Context = + // Inherit data from previous phase. + override def prepareForUnit(tree: Tree)(using Context): Context = val data = UnusedData() - tree.getAttachment(_key).foreach(oldData => + for oldData <- tree.getAttachment(_key) do data.unusedAggregate = oldData.unusedAggregate - ) - val fresh = ctx.fresh.setProperty(_key, data) - tree.putAttachment(_key, data) - fresh - - // ========== END + REPORTING ========== + ctx.fresh.setProperty(_key, data).tap(_ => tree.putAttachment(_key, data)) - override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = - unusedDataApply { ud => + // Report if we are last phase + override def transformUnit(tree: Tree)(using Context): tree.type = + preparing: ud.finishAggregation() - if(phaseMode == PhaseMode.Report) then + if phaseMode == PhaseMode.Report then ud.unusedAggregate.foreach(reportUnused) - } tree - // ========== MiniPhase Prepare ========== - override def prepareForOther(tree: tpd.Tree)(using Context): Context = - // A standard tree traverser covers cases not handled by the Mega/MiniPhase - traverser.traverse(tree) - ctx - - override def prepareForInlined(tree: tpd.Inlined)(using Context): Context = - traverser.traverse(tree.call) - ctx - - override def prepareForIdent(tree: tpd.Ident)(using Context): Context = - if tree.symbol.exists then - unusedDataApply { ud => - @tailrec + override def transformIdent(tree: Ident)(using Context): tree.type = + preparing: + if tree.symbol.exists then def loopOnNormalizedPrefixes(prefix: Type, depth: Int): Unit = // limit to 10 as failsafe for the odd case where there is an infinite cycle if depth < 10 && prefix.exists then - ud.registerUsed(prefix.classSymbol, None) + ud.registerUsed(prefix.classSymbol, name = None, prefix, tree = tree) loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1) + val prefix = tree.typeOpt.normalizedPrefix + loopOnNormalizedPrefixes(prefix, depth = 0) + ud.registerUsed(tree.symbol, Some(tree.name), tree.typeOpt.importPrefix.skipPackageObject, tree = tree) + else if tree.hasType then + ud.registerUsed(tree.tpe.classSymbol, Some(tree.name), tree.tpe.importPrefix.skipPackageObject, tree = tree) + tree - loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) - ud.registerUsed(tree.symbol, Some(tree.name)) - } - else if tree.hasType then - unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some(tree.name))) - else - ctx + override def transformSelect(tree: Select)(using Context): tree.type = + preparing: + val name = tree.removeAttachment(OriginalName) + ud.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic, tree = tree) + tree - override def prepareForSelect(tree: tpd.Select)(using Context): Context = - val name = tree.removeAttachment(OriginalName) - unusedDataApply(_.registerUsed(tree.symbol, name, includeForImport = tree.qualifier.span.isSynthetic)) + override def transformApply(tree: Apply)(using Context): Tree = + tree - override def prepareForBlock(tree: tpd.Block)(using Context): Context = - pushInBlockTemplatePackageDef(tree) + override def transformTyped(tree: Typed)(using Context): Tree = + tree match + case Typed(expr, tpt) => + tree - override def prepareForTemplate(tree: tpd.Template)(using Context): Context = - pushInBlockTemplatePackageDef(tree) + override def transformAssign(tree: Assign)(using Context): tree.type = + preparing: + val sym = tree.lhs.symbol + if sym.exists then + ud.registerSetVar(sym) + tree - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = - pushInBlockTemplatePackageDef(tree) + override def prepareForBlock(tree: Block)(using Context): Context = + pushScope(tree) - override def prepareForValDef(tree: tpd.ValDef)(using Context): Context = - unusedDataApply{ud => - // do not register the ValDef generated for `object` - traverseAnnotations(tree.symbol) - if !tree.symbol.is(Module) then - ud.registerDef(tree) - if tree.name.startsWith("derived$") && tree.typeOpt != NoType then - ud.registerUsed(tree.typeOpt.typeSymbol, None, isDerived = true) - ud.addIgnoredUsage(tree.symbol) - } + override def transformBlock(tree: Block)(using Context): tree.type = + popScope(tree) + tree - override def prepareForDefDef(tree: tpd.DefDef)(using Context): Context = - unusedDataApply: ud => - if !tree.symbol.is(Private) then - tree.termParamss.flatten.foreach { p => - ud.addIgnoredParam(p.symbol) - } - ud.registerTrivial(tree) + override def transformInlined(tree: Inlined)(using Context): tree.type = + transformAllDeep(tree.call) + tree + + override def prepareForTypeTree(tree: TypeTree)(using Context): Context = ctx + + override def transformTypeTree(tree: TypeTree)(using Context): tree.type = + tree.tpe match + case AnnotatedType(_, annot) => transformAllDeep(annot.tree) + case _ => + tree + + override def transformBind(tree: Bind)(using Context): tree.type = + preparing: traverseAnnotations(tree.symbol) - ud.registerDef(tree) + ud.registerPatVar(tree) + tree + + override def prepareForValDef(tree: ValDef)(using Context): Context = + preparing: ud.addIgnoredUsage(tree.symbol) - override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = - unusedDataApply: ud => + override def transformValDef(tree: ValDef)(using Context): tree.type = + preparing: traverseAnnotations(tree.symbol) - if !tree.symbol.is(Param) then // Ignore type parameter (as Scala 2) + if !tree.symbol.is(Module) then // do not register the ValDef generated for `object` ud.registerDef(tree) - ud.addIgnoredUsage(tree.symbol) - - override def prepareForBind(tree: tpd.Bind)(using Context): Context = - traverseAnnotations(tree.symbol) - unusedDataApply(_.registerPatVar(tree)) - - override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context = - if !tree.isInstanceOf[tpd.InferredTypeTree] then typeTraverser(unusedDataApply).traverse(tree.tpe) - ctx - - override def prepareForAssign(tree: tpd.Assign)(using Context): Context = - unusedDataApply{ ud => - val sym = tree.lhs.symbol - if sym.exists then - ud.registerSetVar(sym) - } + if tree.name.startsWith("derived$") && tree.hasType then + def core(t: Tree): (Symbol, Option[Name], Type) = t match + case Ident(name) => (t.tpe.typeSymbol, Some(name), t.tpe.underlyingPrefix) + case Select(t, _) => core(t) + case _ => (NoSymbol, None, NoType) + tree.getAttachment(OriginalTypeClass) match + case Some(orig) => + val (typsym, name, prefix) = core(orig) + ud.registerUsed(typsym, name, prefix.skipPackageObject, tree = EmptyTree) + case _ => + ud.removeIgnoredUsage(tree.symbol) + tree - // ========== MiniPhase Transform ========== + override def prepareForDefDef(tree: DefDef)(using Context): Context = + preparing: + ud.registerTrivial(tree) + if !tree.symbol.is(Private) then + tree.termParamss.flatten.foreach(p => ud.addIgnoredParam(p.symbol)) + ud.addIgnoredUsage(tree.symbol) - override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def transformDefDef(tree: DefDef)(using Context): tree.type = + preparing: + traverseAnnotations(tree.symbol) + ud.registerDef(tree) + ud.removeIgnoredUsage(tree.symbol) tree - override def transformTemplate(tree: tpd.Template)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() - tree + override def prepareForTypeDef(tree: TypeDef)(using Context): Context = + preparing: + ud.addIgnoredUsage(tree.symbol) - override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def transformTypeDef(tree: TypeDef)(using Context): tree.type = + preparing: + traverseAnnotations(tree.symbol) + if !tree.symbol.is(Param) then // type parameter to do? + ud.registerDef(tree) + ud.removeIgnoredUsage(tree.symbol) tree - override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) - tree + override def prepareForTemplate(tree: Template)(using Context): Context = + pushScope(tree, tree.parents) - override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def transformTemplate(tree: Template)(using Context): Tree = + popScope(tree) tree - override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) - tree + override def prepareForPackageDef(tree: PackageDef)(using Context): Context = + pushScope(tree) + override def transformPackageDef(tree: PackageDef)(using Context): Tree = + popScope(tree) + tree - // ---------- MiniPhase HELPERS ----------- + override def prepareForStats(trees: List[Tree])(using Context): Context = ctx - private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = - unusedDataApply { ud => - ud.pushScope(UnusedData.ScopeType.fromTree(tree)) - } - ctx + override def transformStats(trees: List[Tree])(using Context): List[Tree] = + super.transformStats(trees) + trees - private def popOutBlockTemplatePackageDef()(using Context): Context = - unusedDataApply { ud => - ud.popScope() - } - ctx + override def transformOther(tree: Tree)(using Context): tree.type = + preparing: + tree match + case imp: Import => + ud.registerImport(imp) + transformAllDeep(imp.expr) + for selector <- imp.selectors do + if selector.isGiven then + selector.bound match + case untpd.TypedSplice(bound) => transformAllDeep(bound) + case _ => + case AppliedTypeTree(tpt, args) => + transformAllDeep(tpt) + args.foreach(transformAllDeep) + case RefinedTypeTree(tpt, refinements) => + transformAllDeep(tpt) + refinements.foreach(transformAllDeep) + case LambdaTypeTree(tparams, body) => + tparams.foreach(transformAllDeep) + transformAllDeep(body) + case SingletonTypeTree(ref) => + transformAllDeep(ref) + case TypeBoundsTree(lo, hi, alias) => + transformAllDeep(lo) + transformAllDeep(hi) + transformAllDeep(alias) + case tree: NamedArg => transformAllDeep(tree.arg) + case _: InferredTypeTree => + case _ if tree.isType => + //println(s"OTHER TYPE ${tree.getClass} ${tree.show}") + case _ => + //println(s"OTHER ${tree.getClass} ${tree.show}") + tree - /** - * This traverse is the **main** component of this phase - * - * It traverse the tree the tree and gather the data in the - * corresponding context property - */ - private def traverser = new TreeTraverser: - import tpd.* - import UnusedData.ScopeType + private def pushScope(tree: Block | Template | PackageDef, parents: List[Tree] = Nil)(using Context): Context = + preparing: + ud.pushScope(UnusedData.ScopeType.fromTree(tree), parents) - /* Register every imports, definition and usage */ - override def traverse(tree: tpd.Tree)(using Context): Unit = - val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx - tree match - case imp: tpd.Import => - unusedDataApply(_.registerImport(imp)) - imp.selectors.filter(_.isGiven).map(_.bound).collect { - case untpd.TypedSplice(tree1) => tree1 - }.foreach(traverse(_)(using newCtx)) - traverseChildren(tree)(using newCtx) - case ident: Ident => - prepareForIdent(ident) - traverseChildren(tree)(using newCtx) - case sel: Select => - prepareForSelect(sel) - traverseChildren(tree)(using newCtx) - case tree: (tpd.Block | tpd.Template | tpd.PackageDef) => - //! DIFFERS FROM MINIPHASE - pushInBlockTemplatePackageDef(tree) - traverseChildren(tree)(using newCtx) - popOutBlockTemplatePackageDef() - case t: tpd.ValDef => - prepareForValDef(t) - traverseChildren(tree)(using newCtx) - transformValDef(t) - case t: tpd.DefDef => - prepareForDefDef(t) - traverseChildren(tree)(using newCtx) - transformDefDef(t) - case t: tpd.TypeDef => - prepareForTypeDef(t) - traverseChildren(tree)(using newCtx) - transformTypeDef(t) - case t: tpd.Bind => - prepareForBind(t) - traverseChildren(tree)(using newCtx) - case t:tpd.Assign => - prepareForAssign(t) - traverseChildren(tree) - case _: tpd.InferredTypeTree => - case t@tpd.RefinedTypeTree(tpt, refinements) => - //! DIFFERS FROM MINIPHASE - typeTraverser(unusedDataApply).traverse(t.tpe) - traverse(tpt)(using newCtx) - case t@tpd.TypeTree() => - //! DIFFERS FROM MINIPHASE - typeTraverser(unusedDataApply).traverse(t.tpe) - traverseChildren(tree)(using newCtx) - case _ => - //! DIFFERS FROM MINIPHASE - traverseChildren(tree)(using newCtx) - end traverse - end traverser - - /** This is a type traverser which catch some special Types not traversed by the term traverser above */ - private def typeTraverser(dt: (UnusedData => Any) => Unit)(using Context) = new TypeTraverser: - override def traverse(tp: Type): Unit = - if tp.typeSymbol.exists then dt(_.registerUsed(tp.typeSymbol, Some(tp.typeSymbol.name))) - tp match - case AnnotatedType(_, annot) => - dt(_.registerUsed(annot.symbol, None)) - traverseChildren(tp) - case _ => - traverseChildren(tp) + private def popScope(tree: Block | Template | PackageDef)(using Context): Context = + preparing: + ud.popScope(UnusedData.ScopeType.fromTree(tree)) - /** This traverse the annotations of the symbol */ private def traverseAnnotations(sym: Symbol)(using Context): Unit = - sym.denot.annotations.foreach(annot => traverser.traverse(annot.tree)) - + for annot <- sym.denot.annotations do + transformAllDeep(annot.tree) /** Do the actual reporting given the result of the anaylsis */ private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit = - res.warnings.toList.sortBy(_.pos.span.point)(using Ordering[Int]).foreach { s => - s match - case UnusedSymbol(t, _, WarnTypes.Imports) => - report.warning(UnusedSymbolMessage.imports, t) - case UnusedSymbol(t, _, WarnTypes.LocalDefs) => - report.warning(UnusedSymbolMessage.localDefs, t) - case UnusedSymbol(t, _, WarnTypes.ExplicitParams) => - report.warning(UnusedSymbolMessage.explicitParams, t) - case UnusedSymbol(t, _, WarnTypes.ImplicitParams) => - report.warning(UnusedSymbolMessage.implicitParams, t) - case UnusedSymbol(t, _, WarnTypes.PrivateMembers) => - report.warning(UnusedSymbolMessage.privateMembers, t) - case UnusedSymbol(t, _, WarnTypes.PatVars) => - report.warning(UnusedSymbolMessage.patVars, t) - case UnusedSymbol(t, _, WarnTypes.UnsetLocals) => - report.warning("unset local variable, consider using an immutable val instead", t) - case UnusedSymbol(t, _, WarnTypes.UnsetPrivates) => - report.warning("unset private variable, consider using an immutable val instead", t) - } + def messageFor(w: WarnTypes): Message = + import WarnTypes.*, UnusedSymbolMessage.* + w match + case Imports => imports + case LocalDefs => localDefs + case ExplicitParams => explicitParams + case ImplicitParams => implicitParams + case PrivateMembers => privateMembers + case PatVars => patVars + case UnsetLocals => "unset local variable, consider using an immutable val instead".toMessage + case UnsetPrivates => "unset private variable, consider using an immutable val instead".toMessage + res.warnings.toArray.sortInPlaceBy(_.pos.span.point).foreach: + case UnusedSymbol(pos, _, warnType) => + report.warning(messageFor(warnType), pos) end CheckUnused @@ -330,105 +278,112 @@ object CheckUnused: case UnsetLocals case UnsetPrivates - /** - * The key used to retrieve the "unused entity" analysis metadata, - * from the compilation `Context` - */ + /** The key used to retrieve the "unused entity" analysis metadata from the compilation `Context` */ private val _key = Property.StickyKey[UnusedData] + /** Attachment holding the name of an Ident as written by the user. */ val OriginalName = Property.StickyKey[Name] + /** Attachment holding the name of a type class as written by the user. */ + val OriginalTypeClass = Property.StickyKey[Tree] + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key) class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key) - /** - * A stateful class gathering the infos on : - * - imports - * - definitions - * - usage + /** Track usages at a Context. + * + * For an ImportContext, which selectors have been used for lookups? + * + * For other contexts, which symbols defined here have been referenced? */ - private class UnusedData: - import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer => MutList} + private class UnusedData(using Context @constructorOnly): + import collection.mutable as mut, mut.Stack, mut.ListBuffer import UnusedData.* /** The current scope during the tree traversal */ - val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other) + val currScopeType: Stack[ScopeType] = Stack(ScopeType.Other) + inline def peekScopeType = currScopeType.top var unusedAggregate: Option[UnusedResult] = None - /* IMPORTS */ - private val impInScope = MutStack(MutList[ImportSelectorData]()) - /** - * We store the symbol along with their accessibility without import. - * Accessibility to their definition in outer context/scope - * - * See the `isAccessibleAsIdent` extension method below in the file - */ - private val usedInScope = MutStack(MutSet[(Symbol, Option[Name], Boolean)]()) - private val usedInPosition = MutMap.empty[Name, MutSet[Symbol]] + // Trees of superclass constructors, i.e., template.parents when currScopeType is Template. + // Ideally, Context would supply correct context and scope; instead, trees in superclass context + // are promoted to "enclosing scope" by popScope. (This is just for import usage, so class params are ignored.) + private val parents = Stack(List.empty[Tree]) + + private val impInScope = Stack(ListBuffer.empty[ImportSelectorData]) + private val usedInScope = Stack(mut.Map.empty[Symbol, ListBuffer[Usage]]) + private val usedInPosition = mut.Map.empty[Name, mut.Set[Symbol]] /* unused import collected during traversal */ - private val unusedImport = MutList.empty[ImportSelectorData] + private val unusedImport = ListBuffer.empty[ImportSelectorData] - /* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */ - private val localDefInScope = MutList.empty[tpd.MemberDef] - private val privateDefInScope = MutList.empty[tpd.MemberDef] - private val explicitParamInScope = MutList.empty[tpd.MemberDef] - private val implicitParamInScope = MutList.empty[tpd.MemberDef] - private val patVarsInScope = MutList.empty[tpd.Bind] + private val localDefInScope = ListBuffer.empty[MemberDef] + private val privateDefInScope = ListBuffer.empty[MemberDef] + private val explicitParamInScope = ListBuffer.empty[MemberDef] + private val implicitParamInScope = ListBuffer.empty[MemberDef] + private val patVarsInScope = ListBuffer.empty[Bind] /** All variables sets*/ - private val setVars = MutSet[Symbol]() + private val setVars = mut.Set.empty[Symbol] /** All used symbols */ - private val usedDef = MutSet[Symbol]() - /** Do not register as used */ - private val doNotRegister = MutSet[Symbol]() + private val usedDef = mut.Set.empty[Symbol] - /** Trivial definitions, avoid registering params */ - private val trivialDefs = MutSet[Symbol]() + /** Do not register as used. + * + * Seed with common symbols that are never warnable, as an optimization. + */ + private val doNotRegister = mut.Set[Symbol](defn.SourceFileAnnot, defn.ModuleSerializationProxyClass) + private val doNotRegisterPrefix = mut.Set[Symbol](defn.ScalaRuntimePackageClass) - private val paramsToSkip = MutSet[Symbol]() + /** Trivial definitions, avoid registering params */ + private val trivialDefs = mut.Set.empty[Symbol] + private val paramsToSkip = mut.Set.empty[Symbol] def finishAggregation(using Context)(): Unit = - val unusedInThisStage = this.getUnused - this.unusedAggregate match { + unusedAggregate = unusedAggregate match case None => - this.unusedAggregate = Some(unusedInThisStage) + Some(getUnused) case Some(prevUnused) => - val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings) - this.unusedAggregate = Some(UnusedResult(intersection)) - } + Some(UnusedResult(getUnused.warnings.intersect(prevUnused.warnings))) + def registerSelectors(selectors: List[ImportSelectorData]): this.type = + impInScope.top.prependAll(selectors) + this - /** - * Register a found (used) symbol along with its name + /** Register a found (used) symbol along with its name. * - * The optional name will be used to target the right import - * as the same element can be imported with different renaming + * The optional name will be used to target the right import + * as the same element can be imported with different renaming. */ - def registerUsed(sym: Symbol, name: Option[Name], includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit = - if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) then + def registerUsed(sym: Symbol, name: Option[Name], prefix: Type = NoType, includeForImport: Boolean = true, tree: Tree)(using Context): Unit = + if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) && !doNotRegisterPrefix(prefix.typeSymbol) then if sym.isConstructor then - registerUsed(sym.owner, None, includeForImport) // constructor are "implicitly" imported with the class + // constructors are "implicitly" imported with the class + registerUsed(sym.owner, name = None, prefix, includeForImport = includeForImport, tree = tree) else // If the symbol is accessible in this scope without an import, do not register it for unused import analysis val includeForImport1 = - includeForImport - && (name.exists(_.toTermName != sym.name.toTermName) || !sym.isAccessibleAsIdent) + includeForImport && (name.exists(_.toTermName != sym.name.toTermName) || !sym.isAccessibleAsIdent) def addIfExists(sym: Symbol): Unit = if sym.exists then usedDef += sym if includeForImport1 then - usedInScope.top += ((sym, name, isDerived)) + addUsage(Usage(sym, name, prefix, isSuper = !tree.isEmpty && parents.top.exists(t => t.find(_ eq tree).isDefined))) addIfExists(sym) addIfExists(sym.companionModule) addIfExists(sym.companionClass) if sym.sourcePos.exists then for n <- name do - usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym + usedInPosition.getOrElseUpdate(n, mut.Set.empty) += sym + + def addUsage(usage: Usage)(using Context): Unit = + val usages = usedInScope.top.getOrElseUpdate(usage.symbol, ListBuffer.empty) + if !usages.exists(x => x.name == usage.name && x.prefix =:= usage.prefix && x.isSuper == usage.isSuper) + then usages += usage /** Register a symbol that should be ignored */ def addIgnoredUsage(sym: Symbol)(using Context): Unit = @@ -442,32 +397,31 @@ object CheckUnused: paramsToSkip += sym /** Register an import */ - def registerImport(imp: tpd.Import)(using Context): Unit = + def registerImport(imp: Import)(using Context): Unit = if - !tpd.languageImport(imp.expr).nonEmpty + !languageImport(imp.expr).nonEmpty && !imp.isGeneratedByEnum && !isTransparentAndInline(imp) - && currScopeType.top != ScopeType.ReplWrapper // #18383 Do not report top-level import's in the repl as unused + && peekScopeType != ScopeType.ReplWrapper // #18383 Do not report top-level import's in the repl as unused then val qualTpe = imp.expr.tpe // Put wildcard imports at the end, because they have lower priority within one Import - val reorderdSelectors = + val reorderedSelectors = val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard) nonWildcardSels ::: wildcardSels val newDataInScope = - for sel <- reorderdSelectors yield + for sel <- reorderedSelectors yield val data = new ImportSelectorData(qualTpe, sel) - if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then - // Immediately mark the selector as used - data.markUsed() + if shouldSelectorBeReported(imp, sel) || sel.isImportExclusion || isImportIgnored(imp, sel) then + data.markUsed() // Immediately mark the selector as used data - impInScope.top.prependAll(newDataInScope) + registerSelectors(newDataInScope) end registerImport /** Register (or not) some `val` or `def` according to the context, scope and flags */ - def registerDef(memDef: tpd.MemberDef)(using Context): Unit = + def registerDef(memDef: MemberDef)(using Context): Unit = if memDef.isValidMemberDef && !isDefIgnored(memDef) then if memDef.isValidParam then if memDef.symbol.isOneOf(GivenOrImplicit) then @@ -475,148 +429,115 @@ object CheckUnused: implicitParamInScope += memDef else if !paramsToSkip.contains(memDef.symbol) then explicitParamInScope += memDef - else if currScopeType.top == ScopeType.Local then + else if peekScopeType == ScopeType.Local then localDefInScope += memDef else if memDef.shouldReportPrivateDef then privateDefInScope += memDef /** Register pattern variable */ - def registerPatVar(patvar: tpd.Bind)(using Context): Unit = - if !patvar.symbol.isUnusedAnnot then + def registerPatVar(patvar: Bind)(using Context): Unit = + if !patvar.symbol.hasUnusedAnnot then patVarsInScope += patvar /** enter a new scope */ - def pushScope(newScopeType: ScopeType): Unit = - // unused imports : + def pushScope(newScopeType: ScopeType, parents: List[Tree]): Unit = currScopeType.push(newScopeType) - impInScope.push(MutList()) - usedInScope.push(MutSet()) + impInScope.push(ListBuffer.empty) + usedInScope.push(mut.Map.empty) + this.parents.push(parents) def registerSetVar(sym: Symbol): Unit = setVars += sym - /** - * leave the current scope and do : - * - * - If there are imports in this scope check for unused ones + /** Leave current scope and mark any used imports; collect unused imports. */ - def popScope()(using Context): Unit = - currScopeType.pop() - val usedInfos = usedInScope.pop() + def popScope(scopeType: ScopeType)(using Context): Unit = + assert(currScopeType.pop() == scopeType) val selDatas = impInScope.pop() - for usedInfo <- usedInfos do - val (sym, optName, isDerived) = usedInfo - val usedData = selDatas.find { selData => - sym.isInImport(selData, optName, isDerived) - } - usedData match - case Some(data) => - data.markUsed() + for usedInfos <- usedInScope.pop().valuesIterator; usedInfo <- usedInfos do + import usedInfo.* + if isSuper then + addUsage(Usage(symbol, name, prefix, isSuper = false)) // approximate superclass context + else selDatas.find(symbol.isInImport(_, name, prefix)) match + case Some(sel) => + sel.markUsed() case None => // Propagate the symbol one level up if usedInScope.nonEmpty then - usedInScope.top += usedInfo - end for // each in `used` + addUsage(usedInfo) + end for // each in usedInfos for selData <- selDatas do if !selData.isUsed then unusedImport += selData + + this.parents.pop() end popScope - /** - * Leave the scope and return a `List` of unused `ImportSelector`s - * - * The given `List` is sorted by line and then column of the position + /** Leave the scope and return a result set of warnings. */ - def getUnused(using Context): UnusedResult = - popScope() + popScope(ScopeType.Other) // sentinel def isUsedInPosition(name: Name, span: Span): Boolean = usedInPosition.get(name) match case Some(syms) => syms.exists(sym => span.contains(sym.span)) case None => false - val sortedImp = - if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then - unusedImport.toList - .map(d => UnusedSymbol(d.selector.srcPos, d.selector.name, WarnTypes.Imports)) - else - Nil + val warnings = Set.newBuilder[UnusedSymbol] + + if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then + warnings.addAll(unusedImport.iterator.map(d => UnusedSymbol(d.selector.srcPos, d.selector.name, WarnTypes.Imports))) + // Partition to extract unset local variables from usedLocalDefs - val (usedLocalDefs, unusedLocalDefs) = - if ctx.settings.WunusedHas.locals then - localDefInScope.toList.partition(d => d.symbol.usedDefContains) - else - (Nil, Nil) - val sortedLocalDefs = - unusedLocalDefs - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)) - val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList - - val sortedExplicitParams = - if ctx.settings.WunusedHas.explicits then - explicitParamInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)) - else - Nil - val sortedImplicitParams = - if ctx.settings.WunusedHas.implicits then - implicitParamInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)) - else - Nil + if ctx.settings.WunusedHas.locals then + for d <- localDefInScope do + if d.symbol.usedDefContains then + if isUnsetVarDef(d) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)) + else + if !isUsedInPosition(d.symbol.name, d.span) && !containsSyntheticSuffix(d.symbol) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)) + + if ctx.settings.WunusedHas.explicits then + for d <- explicitParamInScope do + if !d.symbol.usedDefContains && !isUsedInPosition(d.symbol.name, d.span) && !containsSyntheticSuffix(d.symbol) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)) + + if ctx.settings.WunusedHas.implicits then + for d <- implicitParamInScope do + if !d.symbol.usedDefContains && !containsSyntheticSuffix(d.symbol) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)) + // Partition to extract unset private variables from usedPrivates - val (usedPrivates, unusedPrivates) = - if ctx.settings.WunusedHas.privates then - privateDefInScope.toList.partition(d => d.symbol.usedDefContains) - else - (Nil, Nil) - val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)) - val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)) - val sortedPatVars = - if ctx.settings.WunusedHas.patvars then - patVarsInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)) - else - Nil - val warnings = - sortedImp ::: - sortedLocalDefs ::: - sortedExplicitParams ::: - sortedImplicitParams ::: - sortedPrivateDefs ::: - sortedPatVars ::: - unsetLocalDefs ::: - unsetPrivateDefs - UnusedResult(warnings.toSet) - end getUnused - //============================ HELPERS ==================================== + if ctx.settings.WunusedHas.privates then + for d <- privateDefInScope do + if d.symbol.usedDefContains then + if isUnsetVarDef(d) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)) + else + if !containsSyntheticSuffix(d.symbol) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)) + if ctx.settings.WunusedHas.patvars then + for d <- patVarsInScope do + if !d.symbol.usedDefContains && !isUsedInPosition(d.symbol.name, d.span) && !containsSyntheticSuffix(d.symbol) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)) - /** - * Checks if import selects a def that is transparent and inline + UnusedResult(warnings.result) + end getUnused + + /** Checks if import selects a def that is transparent and inline. */ - private def isTransparentAndInline(imp: tpd.Import)(using Context): Boolean = + private def isTransparentAndInline(imp: Import)(using Context): Boolean = imp.selectors.exists { sel => val qual = imp.expr val importedMembers = qual.tpe.member(sel.name).alternatives.map(_.symbol) - importedMembers.exists(s => s.is(Transparent) && s.is(Inline)) + importedMembers.exists(_.isAllOf(Transparent | Inline)) } - /** - * Heuristic to detect synthetic suffixes in names of symbols + /** Heuristic to detect synthetic suffixes in names of symbols. */ private def containsSyntheticSuffix(symbol: Symbol)(using Context): Boolean = symbol.name.mangledString.contains("$") @@ -646,110 +567,83 @@ object CheckUnused: private def isConstructorOfSynth(sym: Symbol)(using Context): Boolean = sym.exists && sym.isConstructor && sym.owner.isPackageObject && sym.owner.is(Synthetic) - /** - * This is used to avoid reporting the parameters of the synthetic main method - * generated by `@main` + /** This is used to avoid reporting the parameters of the synthetic main method generated by `@main`. */ private def isSyntheticMainParam(sym: Symbol)(using Context): Boolean = sym.exists && ctx.platform.isMainMethod(sym.owner) && sym.owner.is(Synthetic) - /** - * This is used to ignore exclusion imports (i.e. import `qual`.{`member` => _}) - */ - private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match - case untpd.Ident(name) => name == StdNames.nme.WILDCARD - case _ => false - - /** - * If -Wunused:strict-no-implicit-warn import and this import selector could potentially import implicit. - * return true + /** If -Wunused:strict-no-implicit-warn import and this import selector could potentially import implicit. */ - private def shouldSelectorBeReported(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = + private def shouldSelectorBeReported(imp: Import, sel: ImportSelector)(using Context): Boolean = ctx.settings.WunusedHas.strictNoImplicitWarn && ( sel.isWildcard || imp.expr.tpe.member(sel.name.toTermName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) || imp.expr.tpe.member(sel.name.toTypeName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) ) - /** - * Ignore CanEqual imports + /** Ignore CanEqual imports. */ - private def isImportIgnored(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = - (sel.isWildcard && sel.isGiven && imp.expr.tpe.allMembers.exists(p => p.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) && p.symbol.isOneOf(GivenOrImplicit))) || - (imp.expr.tpe.member(sel.name.toTermName).alternatives - .exists(p => p.symbol.isOneOf(GivenOrImplicit) && p.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)))) + private def derivesFromCanEqual(sym: Symbol)(using Context): Boolean = + sym.isOneOf(GivenOrImplicit) && sym.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) - /** - * Ignore definitions of CanEqual given + private def isImportIgnored(imp: Import, sel: ImportSelector)(using Context): Boolean = + sel.isWildcard && sel.isGiven && imp.expr.tpe.allMembers.exists(p => derivesFromCanEqual(p.symbol)) + || + imp.expr.tpe.member(sel.name.toTermName).alternatives.exists(p => derivesFromCanEqual(p.symbol)) + + /** Ignore definitions of CanEqual given. */ - private def isDefIgnored(memDef: tpd.MemberDef)(using Context): Boolean = - memDef.symbol.isOneOf(GivenOrImplicit) && memDef.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) + private def isDefIgnored(memDef: MemberDef)(using Context): Boolean = derivesFromCanEqual(memDef.symbol) - extension (tree: ImportSelector) - def boundTpe: Type = tree.bound match { - case untpd.TypedSplice(tree1) => tree1.tpe + extension (sel: ImportSelector) + def boundTpe: Type = sel.bound match + case untpd.TypedSplice(tree) => tree.tpe case _ => NoType - } + /** This is used to ignore exclusion imports of the form import `qual`.{`member` => _} + * because `sel.isUnimport` is too broad for old style `import concurrent._`. + */ + def isImportExclusion: Boolean = sel.renamed match + case untpd.Ident(nme.WILDCARD) => true + case _ => false extension (sym: Symbol) - /** is accessible without import in current context */ - private def isAccessibleAsIdent(using Context): Boolean = - ctx.outersIterator.exists{ c => - c.owner == sym.owner - || sym.owner.isClass && c.owner.isClass - && c.owner.thisType.baseClasses.contains(sym.owner) - && c.owner.thisType.member(sym.name).alternatives.contains(sym) - } - /** Given an import and accessibility, return selector that matches import<->symbol */ - private def isInImport(selData: ImportSelectorData, altName: Option[Name], isDerived: Boolean)(using Context): Boolean = + /** Given an import selector, is the symbol imported from the given prefix, optionally with a specific name? + * If isDerived, then it may be an aliased type in source but we only witness it dealiased. + */ + private def isInImport(selData: ImportSelectorData, altName: Option[Name], prefix: Type)(using Context): Boolean = assert(sym.exists) val selector = selData.selector - if !selector.isWildcard then - if altName.exists(explicitName => selector.rename != explicitName.toTermName) then - // if there is an explicit name, it must match - false - else - if isDerived then - // See i15503i.scala, grep for "package foo.test.i17156" - selData.allSymbolsDealiasedForNamed.contains(dealias(sym)) - else - selData.allSymbolsForNamed.contains(sym) - else - // Wildcard - if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then - // The qualifier does not have the target symbol as a member - false - else - if selector.isGiven then - // Further check that the symbol is a given or implicit and conforms to the bound + if selector.isWildcard then + selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) && { // The qualifier must have the target symbol as a member + if selector.isGiven then // Further check that the symbol is a given or implicit and conforms to the bound sym.isOneOf(Given | Implicit) && (selector.bound.isEmpty || sym.info.finalResultType <:< selector.boundTpe) + && selData.qualTpe =:= prefix else - // Normal wildcard, check that the symbol is not a given (but can be implicit) - !sym.is(Given) - end if + !sym.is(Given) // Normal wildcard, check that the symbol is not a given (but can be implicit) + } + else + !altName.exists(_.toTermName != selector.rename) && // if there is an explicit name, it must match + selData.qualTpe =:= prefix && selData.allSymbolsForNamed.contains(sym) end isInImport /** Annotated with @unused */ - private def isUnusedAnnot(using Context): Boolean = - sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot) + private def hasUnusedAnnot(using Context): Boolean = + sym.annotations.exists(_.symbol == ctx.definitions.UnusedAnnot) private def shouldNotReportParamOwner(using Context): Boolean = - if sym.exists then + sym.exists && { val owner = sym.owner trivialDefs(owner) || // is a trivial def owner.isPrimaryConstructor || - owner.annotations.exists ( // @depreacated - _.symbol == ctx.definitions.DeprecatedAnnot - ) || + owner.isDeprecated || owner.isAllOf(Synthetic | PrivateLocal) || owner.is(Accessor) || - owner.isOverriden - else - false + owner.isOverridden + } private def usedDefContains(using Context): Boolean = sym.everySymbol.exists(usedDef.apply) @@ -757,21 +651,21 @@ object CheckUnused: private def everySymbol(using Context): List[Symbol] = List(sym, sym.companionClass, sym.companionModule, sym.moduleClass).filter(_.exists) - /** A function is overriden. Either has `override flags` or parent has a matching member (type and name) */ - private def isOverriden(using Context): Boolean = + /** A function is overridden. Either has `override flags` or parent has a matching member (type and name) */ + private def isOverridden(using Context): Boolean = sym.is(Flags.Override) || (sym.exists && sym.owner.thisType.parents.exists(p => sym.matchingMember(p).exists)) end extension - extension (defdef: tpd.DefDef) + extension (defdef: DefDef) // so trivial that it never consumes params private def isTrivial(using Context): Boolean = val rhs = defdef.rhs rhs.symbol == ctx.definitions.Predef_undefined || rhs.tpe =:= ctx.definitions.NothingType || defdef.symbol.is(Deferred) || - (rhs match { - case _: tpd.Literal => true + rhs.match + case _: Literal => true case _ => rhs.tpe match case ConstantType(_) => true case tp: TermRef => @@ -779,15 +673,14 @@ object CheckUnused: tp.underlying.classSymbol.is(Flags.Module) case _ => false - }) def registerTrivial(using Context): Unit = if defdef.isTrivial then trivialDefs += defdef.symbol - extension (memDef: tpd.MemberDef) + extension (memDef: MemberDef) private def isValidMemberDef(using Context): Boolean = memDef.symbol.exists - && !memDef.symbol.isUnusedAnnot + && !memDef.symbol.hasUnusedAnnot && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) && !memDef.name.isWildcard && !memDef.symbol.owner.is(ExtensionMethod) @@ -799,20 +692,23 @@ object CheckUnused: !sym.shouldNotReportParamOwner private def shouldReportPrivateDef(using Context): Boolean = - currScopeType.top == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor) + peekScopeType == ScopeType.Template + && !memDef.symbol.isConstructor + && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor) + && !ignoredNames(memDef.symbol.name.toTermName) private def isUnsetVarDef(using Context): Boolean = val sym = memDef.symbol sym.is(Mutable) && !setVars(sym) - extension (imp: tpd.Import) + extension (imp: Import) /** Enum generate an import for its cases (but outside them), which should be ignored */ def isGeneratedByEnum(using Context): Boolean = imp.symbol.exists && imp.symbol.owner.is(Flags.Enum, butNot = Flags.Case) extension (thisName: Name) private def isWildcard: Boolean = - thisName == StdNames.nme.WILDCARD || thisName.is(WildcardParamName) + thisName == nme.WILDCARD || thisName.is(WildcardParamName) end UnusedData @@ -825,12 +721,14 @@ object CheckUnused: object ScopeType: /** return the scope corresponding to the enclosing scope of the given tree */ - def fromTree(tree: tpd.Tree)(using Context): ScopeType = tree match - case tree: tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template - case _:tpd.Block => Local + def fromTree(tree: Tree)(using Context): ScopeType = tree match + case _: Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template + case _: Block => Local case _ => Other - final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): + val ignoredNames: Set[TermName] = Set("readResolve", "readObject", "writeObject", "writeReplace").map(termName(_)) + + final case class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): private var myUsed: Boolean = false def markUsed(): Unit = myUsed = true @@ -845,25 +743,42 @@ object CheckUnused: myAllSymbols = allDenots.map(_.symbol).toSet myAllSymbols.uncheckedNN - private var myAllSymbolsDealiased: Set[Symbol] | Null = null - - def allSymbolsDealiasedForNamed(using Context): Set[Symbol] = - if myAllSymbolsDealiased == null then - myAllSymbolsDealiased = allSymbolsForNamed.map(sym => dealias(sym)) - myAllSymbolsDealiased.uncheckedNN end ImportSelectorData case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes) /** A container for the results of the used elements analysis */ - case class UnusedResult(warnings: Set[UnusedSymbol]) + class UnusedResult(val warnings: Set[UnusedSymbol]) object UnusedResult: val Empty = UnusedResult(Set.empty) - end UnusedData - - private def dealias(symbol: Symbol)(using Context): Symbol = - if symbol.isType && symbol.asType.denot.isAliasType then - symbol.asType.typeRef.dealias.typeSymbol - else - symbol + /** A symbol usage includes the name under which it was observed, + * and the prefix from which it was selected. + */ + class Usage(val symbol: Symbol, val name: Option[Name], val prefix: Type, val isSuper: Boolean) + end UnusedData + extension (sym: Symbol) + /** is accessible without import in current context */ + def isAccessibleAsIdent(using Context): Boolean = + ctx.outersIterator.exists: c => + c.owner == sym.owner + || sym.owner.isClass && c.owner.isClass + && c.owner.thisType.baseClasses.contains(sym.owner) + && c.owner.thisType.member(sym.name).alternatives.contains(sym) + + extension (tp: Type) + def importPrefix(using Context): Type = tp match + case tp: NamedType => tp.prefix + case tp: ClassInfo => tp.prefix + case tp: TypeProxy => tp.superType.normalizedPrefix + case _ => NoType + def underlyingPrefix(using Context): Type = tp match + case tp: NamedType => tp.prefix + case tp: ClassInfo => tp.prefix + case tp: TypeProxy => tp.underlying.underlyingPrefix + case _ => NoType + def skipPackageObject(using Context): Type = + if tp.typeSymbol.isPackageObject then tp.underlyingPrefix else tp + def underlying(using Context): Type = tp match + case tp: TypeProxy => tp.underlying + case _ => tp end CheckUnused diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 60148319a61c..c28f2b309cb9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -10,8 +10,9 @@ import Contexts.*, Symbols.*, Types.*, SymDenotations.*, Names.*, NameOps.*, Fla import ProtoTypes.*, ContextOps.* import util.Spans.* import util.SrcPos -import collection.mutable +import collection.mutable.ListBuffer import ErrorReporting.errorTree +import transform.CheckUnused.OriginalTypeClass /** A typer mixin that implements type class derivation functionality */ trait Deriving { @@ -25,8 +26,8 @@ trait Deriving { */ class Deriver(cls: ClassSymbol, codePos: SrcPos)(using Context) { - /** A buffer for synthesized symbols for type class instances */ - private var synthetics = new mutable.ListBuffer[Symbol] + /** A buffer for synthesized symbols for type class instances, with what user asked to synthesize. */ + private val synthetics = ListBuffer.empty[(tpd.Tree, Symbol)] /** A version of Type#underlyingClassRef that works also for higher-kinded types */ private def underlyingClassRef(tp: Type): Type = tp match { @@ -41,7 +42,7 @@ trait Deriving { * an instance with the same name does not exist already. * @param reportErrors Report an error if an instance with the same name exists already */ - private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = { + private def addDerivedInstance(derived: tpd.Tree, clsName: Name, info: Type, pos: SrcPos): Unit = { val instanceName = "derived$".concat(clsName) if (ctx.denotNamed(instanceName).exists) report.error(em"duplicate type class derivation for $clsName", pos) @@ -50,9 +51,8 @@ trait Deriving { // derived instance will have too low a priority to be selected over a freshly // derived instance at the summoning site. val flags = if info.isInstanceOf[MethodOrPoly] then GivenMethod else Given | Lazy - synthetics += - newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span) - .entered + val sym = newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span).entered + synthetics += derived -> sym } /** Check derived type tree `derived` for the following well-formedness conditions: @@ -77,7 +77,8 @@ trait Deriving { * that have the same name but different prefixes through selective aliasing. */ private def processDerivedInstance(derived: untpd.Tree): Unit = { - val originalTypeClassType = typedAheadType(derived, AnyTypeConstructorProto).tpe + val originalTypeClassTree = typedAheadType(derived, AnyTypeConstructorProto) + val originalTypeClassType = originalTypeClassTree.tpe val underlyingClassType = underlyingClassRef(originalTypeClassType) val typeClassType = checkClassType( underlyingClassType.orElse(originalTypeClassType), @@ -100,7 +101,7 @@ trait Deriving { val derivedInfo = if derivedParams.isEmpty then monoInfo else PolyType.fromParams(derivedParams, monoInfo) - addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) + addDerivedInstance(originalTypeClassTree, originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) } def deriveSingleParameter: Unit = { @@ -312,7 +313,7 @@ trait Deriving { else tpd.ValDef(sym.asTerm, typeclassInstance(sym)(Nil)) } - synthetics.map(syntheticDef).toList + synthetics.map((t, s) => syntheticDef(s).withAttachment(OriginalTypeClass, t)).toList } def finalize(stat: tpd.TypeDef): tpd.Tree = { diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index e7e5936a4b29..996093e936ea 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -793,13 +793,14 @@ trait ParallelTesting extends RunnerOrchestration { self => diffCheckfile(testSource, reporters, logger) override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = - lazy val (map, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) + lazy val (expected, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) lazy val obtCount = reporters.foldLeft(0)(_ + _.warningCount) - lazy val (expected, unexpected) = getMissingExpectedWarnings(map, reporters.iterator.flatMap(_.diagnostics)) - lazy val diagnostics = reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line).map(e => s" at ${e.pos.line + 1}: ${e.message}")) - def showLines(title: String, lines: Seq[String]) = if lines.isEmpty then "" else title + lines.mkString("\n", "\n", "") - def hasMissingAnnotations = expected.nonEmpty || unexpected.nonEmpty - def showDiagnostics = showLines("-> following the diagnostics:", diagnostics) + lazy val (unfulfilled, unexpected) = getMissingExpectedWarnings(expected, diagnostics.iterator) + lazy val diagnostics = reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line)) + lazy val messages = diagnostics.map(d => s" at ${d.pos.line + 1}: ${d.message}") + def showLines(title: String, lines: Seq[String]) = if lines.isEmpty then "" else lines.mkString(s"$title\n", "\n", "") + def hasMissingAnnotations = unfulfilled.nonEmpty || unexpected.nonEmpty + def showDiagnostics = showLines("-> following the diagnostics:", messages) Option: if reporters.exists(_.errorCount > 0) then s"""Compilation failed for: ${testSource.title} @@ -808,58 +809,63 @@ trait ParallelTesting extends RunnerOrchestration { self => else if expCount != obtCount then s"""|Wrong number of warnings encountered when compiling $testSource |expected: $expCount, actual: $obtCount - |${showLines("Unfulfilled expectations:", expected)} + |${showLines("Unfulfilled expectations:", unfulfilled)} |${showLines("Unexpected warnings:", unexpected)} |$showDiagnostics |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") - else if hasMissingAnnotations then s"\nWarnings found on incorrect row numbers when compiling $testSource\n$showDiagnostics" - else if !map.isEmpty then s"\nExpected warnings(s) have {=}: $map" + else if hasMissingAnnotations then + s"""|Warnings found on incorrect row numbers when compiling $testSource + |${showLines("Unfulfilled expectations:", unfulfilled)} + |${showLines("Unexpected warnings:", unexpected)} + |$showDiagnostics + |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") + else if !expected.isEmpty then s"\nExpected warnings(s) have {=}: $expected" else null end maybeFailureMessage def getWarnMapAndExpectedCount(files: Seq[JFile]): (HashMap[String, Integer], Int) = - val comment = raw"//( *)(nopos-)?warn".r - val map = new HashMap[String, Integer]() + val comment = raw"//(?: *)(nopos-)?warn".r + val map = HashMap[String, Integer]() var count = 0 def bump(key: String): Unit = map.get(key) match case null => map.put(key, 1) case n => map.put(key, n+1) count += 1 - files.filter(isSourceFile).foreach { file => - Using(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => - source.getLines.zipWithIndex.foreach { case (line, lineNbr) => - comment.findAllMatchIn(line).foreach { m => - m.group(2) match - case "nopos-" => - bump("nopos") - case _ => bump(s"${file.getPath}:${lineNbr+1}") - } - } - }.get - } + for file <- files if isSourceFile(file) do + Using.resource(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => + source.getLines.zipWithIndex.foreach: (line, lineNbr) => + comment.findAllMatchIn(line).foreach: + case comment("nopos-") => bump("nopos") + case _ => bump(s"${file.getPath}:${lineNbr+1}") + } + end for (map, count) - def getMissingExpectedWarnings(map: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = - val unexpected, unpositioned = ListBuffer.empty[String] + // return unfulfilled expected warnings and unexpected diagnostics + def getMissingExpectedWarnings(expected: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = + val unexpected = ListBuffer.empty[String] def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator) def seenAt(key: String): Boolean = - map.get(key) match + expected.get(key) match case null => false - case 1 => map.remove(key) ; true - case n => map.put(key, n - 1) ; true + case 1 => expected.remove(key); true + case n => expected.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = val srcpos = d.pos.nonInlined if srcpos.exists then val key = s"${relativize(srcpos.source.file.toString())}:${srcpos.line + 1}" if !seenAt(key) then unexpected += key else - if(!seenAt("nopos")) unpositioned += relativize(srcpos.source.file.toString()) + if !seenAt("nopos") then unexpected += relativize(srcpos.source.file.toString) reporterWarnings.foreach(sawDiagnostic) - (map.asScala.keys.toList, (unexpected ++ unpositioned).toList) + val splitter = raw"(?:[^:]*):(\d+)".r + val unfulfilled = expected.asScala.keys.toList.sortBy { case splitter(n) => n.toInt case _ => -1 } + (unfulfilled, unexpected.toList) end getMissingExpectedWarnings + end WarnTest private final class RewriteTest(testSources: List[TestSource], checkFiles: Map[JFile, JFile], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) { @@ -994,8 +1000,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def seenAt(key: String): Boolean = errorMap.get(key) match case null => false - case 1 => errorMap.remove(key) ; true - case n => errorMap.put(key, n - 1) ; true + case 1 => errorMap.remove(key); true + case n => errorMap.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = d.pos.nonInlined match case srcpos if srcpos.exists => diff --git a/tests/untried/neg/warn-unused-privates.scala b/tests/untried/neg/warn-unused-privates.scala deleted file mode 100644 index 64e7679f37ac..000000000000 --- a/tests/untried/neg/warn-unused-privates.scala +++ /dev/null @@ -1,105 +0,0 @@ -class Bippy(a: Int, b: Int) { - private def this(c: Int) = this(c, c) // warn - private def bippy(x: Int): Int = bippy(x) // TODO: could warn - private def boop(x: Int) = x + a + b // warn - final private val MILLIS1 = 2000 // no warn, might have been inlined - final private val MILLIS2: Int = 1000 // warn - final private val HI_COMPANION: Int = 500 // no warn, accessed from companion - def hi() = Bippy.HI_INSTANCE -} -object Bippy { - def hi(x: Bippy) = x.HI_COMPANION - private val HI_INSTANCE: Int = 500 // no warn, accessed from instance - private val HEY_INSTANCE: Int = 1000 // warn -} - -class A(val msg: String) -class B1(msg: String) extends A(msg) -class B2(msg0: String) extends A(msg0) -class B3(msg0: String) extends A("msg") - -/*** Early defs warnings disabled primarily due to SI-6595. - * The test case is here to assure we aren't issuing false positives; - * the ones labeled "warn" don't warn. - ***/ -class Boppy extends { - private val hmm: String = "abc" // no warn, used in early defs - private val hom: String = "def" // no warn, used in body - private final val him = "ghi" // no warn, might have been (was) inlined - final val him2 = "ghi" // no warn, same - final val himinline = him - private val hum: String = "jkl" // warn - final val ding = hmm.length -} with Mutable { - val dinger = hom - private val hummer = "def" // warn - - private final val bum = "ghi" // no warn, might have been (was) inlined - final val bum2 = "ghi" // no warn, same -} - -trait Accessors { - private var v1: Int = 0 // warn - private var v2: Int = 0 // warn, never set - private var v3: Int = 0 // warn, never got - private var v4: Int = 0 // no warn - - def bippy(): Int = { - v3 = 5 - v4 = 6 - v2 + v4 - } -} - -trait DefaultArgs { - // warn about default getters for x2 and x3 - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 - - def boppy() = bippy(5, 100, 200) -} - -class Outer { - class Inner -} - -trait Locals { - def f0 = { - var x = 1 // warn - var y = 2 - y = 3 - y + y - } - def f1 = { - val a = new Outer // no warn - val b = new Outer // warn - new a.Inner - } - def f2 = { - var x = 100 // warn about it being a var - x - } -} - -object Types { - private object Dongo { def f = this } // warn - private class Bar1 // warn - private class Bar2 // no warn - private type Alias1 = String // warn - private type Alias2 = String // no warn - def bippo = (new Bar2).toString - - def f(x: Alias2) = x.length - - def l1() = { - object HiObject { def f = this } // warn - class Hi { // warn - def f1: Hi = new Hi - def f2(x: Hi) = x - } - class DingDongDoobie // warn - class Bippy // no warn - type Something = Bippy // no warn - type OtherThing = String // warn - (new Bippy): Something - } -} diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index df8691c21a13..ff7d054cfb5d 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -213,7 +213,8 @@ package testImportsInImports: package c: import a.b // OK import b.x // OK - val y = x + import b.x as z // OK + val y = x + z //------------------------------------- package testOnOverloadedMethodsImports: @@ -265,4 +266,4 @@ package foo.test.typeapply.hklamdba.i16680: import foo.IO // OK def f[F[_]]: String = "hello" - def go = f[IO] \ No newline at end of file + def go = f[IO] diff --git a/tests/warn/i15503i.scala b/tests/warn/i15503i.scala index b7981e0e4206..d3701b696009 100644 --- a/tests/warn/i15503i.scala +++ b/tests/warn/i15503i.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all +//> using options -Wunused:all -Ystop-after:checkUnusedPostInlining import collection.mutable.{Map => MutMap} // warn import collection.mutable.Set // warn @@ -82,8 +82,8 @@ package foo.test.i16678: def run = println(foo(number => number.toString, value = 5)) // OK println(foo(number => "", value = 5)) // warn - println(foo(func = number => "", value = 5)) // warn println(foo(func = number => number.toString, value = 5)) // OK + println(foo(func = number => "", value = 5)) // warn println(foo(func = _.toString, value = 5)) // OK package foo.test.possibleclasses: @@ -247,7 +247,7 @@ package foo.test.i16679a: import scala.deriving.Mirror object CaseClassByStringName: inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassByStringName[A] = - new CaseClassByStringName[A]: // warn + new CaseClassByStringName[A]: def name: String = A.toString object secondPackage: @@ -263,7 +263,7 @@ package foo.test.i16679b: object CaseClassName: import scala.deriving.Mirror inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassName[A] = - new CaseClassName[A]: // warn + new CaseClassName[A]: def name: String = A.toString object Foo: @@ -279,7 +279,7 @@ package foo.test.i17156: package a: trait Foo[A] object Foo: - inline def derived[T]: Foo[T] = new Foo{} // warn + inline def derived[T]: Foo[T] = new Foo {} package b: import a.Foo diff --git a/tests/warn/i19657-mega.scala b/tests/warn/i19657-mega.scala new file mode 100644 index 000000000000..681db545920c --- /dev/null +++ b/tests/warn/i19657-mega.scala @@ -0,0 +1,9 @@ +//> using options -Wshadow:type-parameter-shadow -Wunused:all + +class F[X, M[N[X]]]: + private def x[X] = toString // warn // warn + +// the first is spurious +// at 3: Type parameter X for type M shadows the type defined by type X in class F +// at 4: Type parameter X for method x shadows the type defined by type X in class F +// at 4: unused private member diff --git a/tests/warn/i19657.scala b/tests/warn/i19657.scala new file mode 100644 index 000000000000..3e7a774ffc60 --- /dev/null +++ b/tests/warn/i19657.scala @@ -0,0 +1,85 @@ +//> using options -Wunused:imports -Ystop-after:checkUnusedPostInlining + +trait Schema[A] + +case class Foo() +case class Bar() + +trait SchemaGenerator[A] { + given Schema[A] = new Schema[A]{} +} + +object FooCodec extends SchemaGenerator[Foo] +object BarCodec extends SchemaGenerator[Bar] + +def summonSchemas(using Schema[Foo], Schema[Bar]) = () + +def summonSchema(using Schema[Foo]) = () + +def `i19657 check prefix to pick selector`: Unit = + import FooCodec.given + import BarCodec.given + summonSchemas + +def `i19657 regression test`: Unit = + import FooCodec.given + import BarCodec.given // warn + summonSchema + +def `i19657 check prefix to pick specific selector`: Unit = + import FooCodec.given_Schema_A + import BarCodec.given_Schema_A + summonSchemas + +def `same symbol different names`: Unit = + import FooCodec.given_Schema_A + import FooCodec.given_Schema_A as AThing + summonSchema(using given_Schema_A) + summonSchema(using AThing) + +package i17156: + package a: + trait Foo[A] + object Foo: + class Food[A] extends Foo[A] + inline def derived[T]: Foo[T] = Food() + + package b: + import a.Foo + type Xd[A] = Foo[A] + + package c: + import b.Xd + trait Z derives Xd // checks if dealiased import is prefix a.Foo + class Bar extends Xd[Int] // checks if import qual b is prefix of b.Xd + +object Coll: + class C: + type HM[K, V] = scala.collection.mutable.HashMap[K, V] +object CC extends Coll.C +import CC.* + +def `param type is imported`(map: HM[String, String]): Unit = println(map("hello, world")) + +object Constants: + final val i = 42 +def `old-style constants are usages`: Unit = + object Local: + final val j = 27 + import Constants.i + println(i + Local.j) + +object Constantinople: + val k = 42 +class `scope of super`: + import Constants.i // was bad warn + class C(x: Int): + def y = x + class D(j: Int) extends C(i + j): + import Constants.* // does not resolve i in C(i) + def m = i + def f = + import Constantinople.* + class E(e: Int) extends C(i + k): + def g = e + y + k + 1 + E(0).g diff --git a/tests/pos/i20860.scala b/tests/warn/i20860.scala similarity index 78% rename from tests/pos/i20860.scala rename to tests/warn/i20860.scala index 1e1ddea11b75..df08b23c3a61 100644 --- a/tests/pos/i20860.scala +++ b/tests/warn/i20860.scala @@ -1,3 +1,5 @@ +//> using options -Wunused:imports + def `i20860 use result to check selector bound`: Unit = import Ordering.Implicits.given Ordering[?] summon[Ordering[Seq[Int]]] diff --git a/tests/warn/unused-privates.scala b/tests/warn/unused-privates.scala new file mode 100644 index 000000000000..e30c00907b4c --- /dev/null +++ b/tests/warn/unused-privates.scala @@ -0,0 +1,259 @@ +// +//> using options -deprecation -Wunused:privates,locals +// +class Bippy(a: Int, b: Int) { + private def this(c: Int) = this(c, c) // NO warn + private def bippy(x: Int): Int = bippy(x) // warn + private def boop(x: Int) = x+a+b // warn + final private val MILLIS1 = 2000 // warn, scala2: no warn, might have been inlined + final private val MILLIS2: Int = 1000 // warn + final private val HI_COMPANION: Int = 500 // no warn, accessed from companion + def hi() = Bippy.HI_INSTANCE +} +object Bippy { + def hi(x: Bippy) = x.HI_COMPANION + private val HI_INSTANCE: Int = 500 // no warn, accessed from instance + private val HEY_INSTANCE: Int = 1000 // warn + private lazy val BOOL: Boolean = true // warn +} + +class A(val msg: String) +class B1(msg: String) extends A(msg) +class B2(msg0: String) extends A(msg0) +class B3(msg0: String) extends A("msg") + +trait Accessors { + private var v1: Int = 0 // warn + private var v2: Int = 0 // warn, never set + private var v3: Int = 0 // NO warn, never got + private var v4: Int = 0 // no warn + + private var v5 = 0 // warn, never set + private var v6 = 0 // NO warn, never got + private var v7 = 0 // no warn + + def bippy(): Int = { + v3 = 3 + v4 = 4 + v6 = 6 + v7 = 7 + v2 + v4 + v5 + v7 + } +} + +class StableAccessors { + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // NO warn, never got + private var s4: Int = 0 // no warn + + private var s5 = 0 // warn, never set + private var s6 = 0 // no warn, limitation + private var s7 = 0 // no warn + + def bippy(): Int = { + s3 = 3 + s4 = 4 + s6 = 6 + s7 = 7 + s2 + s4 + s5 + s7 + } +} + +trait DefaultArgs { + // NO warn about default getters for x2 and x3 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 + + def boppy() = bippy(5, 100, 200) +} + +/* scala/bug#7707 Both usages warn default arg because using PrivateRyan.apply, not new. +case class PrivateRyan private (ryan: Int = 42) { def f = PrivateRyan() } +object PrivateRyan { def f = PrivateRyan() } +*/ + +class Outer { + class Inner +} + +trait Locals { + def f0 = { + var x = 1 // warn + var y = 2 + y = 3 + y + y + } + def f1 = { + val a = new Outer // no warn + val b = new Outer // warn + new a.Inner + } + def f2 = { + var x = 100 // warn about it being a var + x + } +} + +object Types { + private object Dongo { def f = this } // NO warn + private class Bar1 // warn + private class Bar2 // no warn + private type Alias1 = String // warn + private type Alias2 = String // no warn + def bippo = (new Bar2).toString + + def f(x: Alias2) = x.length + + def l1() = { + object HiObject { def f = this } // NO warn + class Hi { // warn + def f1: Hi = new Hi + def f2(x: Hi) = x + } + class DingDongDoobie // warn + class Bippy // no warn + type Something = Bippy // no warn + type OtherThing = String // warn + (new Bippy): Something + } +} + +trait Underwarn { + def f(): Seq[Int] + + def g() = { + val Seq(_, _) = f() // no warn + true + } +} + +class OtherNames { + private def x_=(i: Int): Unit = () + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () + private def y: Int = 42 + + def f = y +} + +case class C(a: Int, b: String, c: Option[String]) +case class D(a: Int) + +// patvars which used to warn as vals in older scala 2 +trait Boundings { + + def c = C(42, "hello", Some("world")) + def d = D(42) + + def f() = { + val C(x, y, Some(z)) = c: @unchecked // no warn + 17 + } + def g() = { + val C(x @ _, y @ _, Some(z @ _)) = c: @unchecked // no warn + 17 + } + def h() = { + val C(x @ _, y @ _, z @ Some(_)) = c: @unchecked // no warn for z? + 17 + } + + def v() = { + val D(x) = d // no warn + 17 + } + def w() = { + val D(x @ _) = d // no warn + 17 + } + +} + +trait Forever { + def f = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield (i + j) + } + def g = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield 42 // val emitted only if needed, hence nothing unused + } +} + +trait Ignorance { + private val readResolve = 42 // no warn special members +} + +trait CaseyKasem { + def f = 42 match { + case x if x < 25 => "no warn" + case y if toString.nonEmpty => "no warn" + y + case z => "warn" + } +} +trait CaseyAtTheBat { + def f = Option(42) match { + case Some(x) if x < 25 => "no warn" + case Some(y @ _) if toString.nonEmpty => "no warn" + case Some(z) => "warn" + case None => "no warn" + } +} + +class `not even using companion privates` + +object `not even using companion privates` { + private implicit class `for your eyes only`(i: Int) { // no warn deprecated feature + def f = i + } +} + +class `no warn in patmat anonfun isDefinedAt` { + def f(pf: PartialFunction[String, Int]) = pf("42") + def g = f { + case s => s.length // no warn (used to warn case s => true in isDefinedAt) + } +} + +// this is the ordinary case, as AnyRef is an alias of Object +class `nonprivate alias is enclosing` { + class C + type C2 = C + private class D extends C2 // warn +} + +object `classof something` { + private class intrinsically + def f = classOf[intrinsically].toString() +} + +trait `scala 2 short comings` { + def f: Int = { + val x = 42 // warn + 17 + } +} + +class `issue 12600 ignore abstract types` { + type Abs +} + +class `t12992 enclosing def is unused` { + private val n = 42 + @annotation.unused def f() = n + 2 // unused code uses n +} + +class `recursive reference is not a usage` { + private def f(i: Int): Int = // warn + if (i <= 0) i + else f(i-1) + private class P { // warn + def f() = new P() + } +}