diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index d8ba1ab5dc2e..45d58d177712 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 @@ -50,10 +48,10 @@ class Compiler { List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks List(new Inlining) :: // Inline and execute macros List(new PostInlining) :: // Add mirror support for inlined code - List(new CheckUnused.PostInlining) :: // Check for unused elements List(new Staging) :: // Check staging levels and heal staged types List(new Splicing) :: // Replace level 1 splices with holes List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures + List(new CheckUnused.PostInlining) :: // Check for unused elements Nil /** Phases dealing with the transformation from pickled trees to backend trees */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0195a4ddbf34..6ae1435cca7f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -494,6 +494,8 @@ class Definitions { @tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???) @tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass + @tu lazy val SameTypeClass: ClassSymbol = requiredClass("scala.=:=") + @tu lazy val SameType_refl: Symbol = SameTypeClass.companionModule.requiredMethod(nme.refl) @tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<") @tu lazy val SubType_refl: Symbol = SubTypeClass.companionModule.requiredMethod(nme.refl) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 3b7fba1cb52d..9819b8576b8d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3295,14 +3295,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..e7c03a63a628 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,314 +1,271 @@ 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, NoPrefix, 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.core.NameKinds.{ContextFunctionParamName, WildcardParamName} +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.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.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 + ctx.fresh.setProperty(_key, data).tap(_ => tree.putAttachment(_key, data)) - // ========== END + REPORTING ========== - - 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 transformAssign(tree: Assign)(using Context): tree.type = + preparing: + val sym = tree.lhs.symbol + if sym.exists then + ud.registerSetVar(sym) + tree - override def prepareForBlock(tree: tpd.Block)(using Context): Context = - pushInBlockTemplatePackageDef(tree) + override def prepareForBlock(tree: Block)(using Context): Context = + pushScope(tree) - override def prepareForTemplate(tree: tpd.Template)(using Context): Context = - pushInBlockTemplatePackageDef(tree) + override def transformBlock(tree: Block)(using Context): tree.type = + popScope(tree) + tree - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = - pushInBlockTemplatePackageDef(tree) + // Megaphase will feed us Inlined(call, bindings, expansion) bindings and expansion, + // but we care about call only. + override def prepareForInlined(tree: Inlined)(using Context): Context = + preparing: + ud.exclude.top.addAll(tree.bindings) + ud.exclude.top.addOne(tree.expansion) - 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 transformInlined(tree: Inlined)(using Context): tree.type = + if phaseMode == PhaseMode.Aggregate then + transformAllDeep(tree.call) + 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 transformTypeTree(tree: TypeTree)(using Context): tree.type = + tree.tpe match + case AnnotatedType(_, annot) => transformAllDeep(annot.tree) + case tpt if !tree.isInferred && tpt.typeSymbol.exists => + preparing: + ud.registerUsed(tpt.typeSymbol, Some(tpt.typeSymbol.name), tree = tree) // usage was a simple name + 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)) + 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 - override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context = - if !tree.isInstanceOf[tpd.InferredTypeTree] then typeTraverser(unusedDataApply).traverse(tree.tpe) - ctx + override def prepareForDefDef(tree: DefDef)(using Context): Context = + preparing: + ud.registerTrivial(tree) + ud.addIgnoredUsage(tree.symbol) - override def prepareForAssign(tree: tpd.Assign)(using Context): Context = - unusedDataApply{ ud => - val sym = tree.lhs.symbol - if sym.exists then - ud.registerSetVar(sym) - } + override def transformDefDef(tree: DefDef)(using Context): tree.type = + preparing: + traverseAnnotations(tree.symbol) + ud.registerDef(tree) + ud.removeIgnoredUsage(tree.symbol) + tree - // ========== MiniPhase Transform ========== + override def prepareForTypeDef(tree: TypeDef)(using Context): Context = + preparing: + ud.addIgnoredUsage(tree.symbol) - override def transformBlock(tree: tpd.Block)(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 transformTemplate(tree: tpd.Template)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() - tree + override def prepareForTemplate(tree: Template)(using Context): Context = + pushScope(tree, tree.parents) - override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def transformTemplate(tree: Template)(using Context): Tree = + popScope(tree) tree - override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) - tree + override def prepareForPackageDef(tree: PackageDef)(using Context): Context = + pushScope(tree) - override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def transformPackageDef(tree: PackageDef)(using Context): Tree = + popScope(tree) tree - override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + 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 Annotated(arg, annot) => + transformAllDeep(arg) + transformAllDeep(annot) + case Quote(body, tags) => + transformAllDeep(body) + tags.foreach(transformAllDeep) + case Splice(expr) => + transformAllDeep(expr) + case QuotePattern(bindings, body, quotes) => + bindings.foreach(transformAllDeep) + transformAllDeep(body) + transformAllDeep(quotes) + case SplicePattern(body, typeargs, args) => + transformAllDeep(body) + typeargs.foreach(transformAllDeep) + args.foreach(transformAllDeep) + case _: InferredTypeTree => + case _ if tree.isType => + //println(s"OTHER TYPE ${tree.getClass} ${tree.show}") + case _ => + //println(s"OTHER ${tree.getClass} ${tree.show}") tree + private def pushScope(tree: Block | Template | PackageDef, parents: List[Tree] = Nil)(using Context): Context = + preparing: + ud.pushScope(UnusedData.ScopeType.fromTree(tree), parents) - // ---------- MiniPhase HELPERS ----------- - - private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = - unusedDataApply { ud => - ud.pushScope(UnusedData.ScopeType.fromTree(tree)) - } - ctx - - private def popOutBlockTemplatePackageDef()(using Context): Context = - unusedDataApply { ud => - ud.popScope() - } - ctx - - /** - * 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 - - /* 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 +287,116 @@ 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] + + /** 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) /** Trivial definitions, avoid registering params */ - private val trivialDefs = MutSet[Symbol]() + private val trivialDefs = mut.Set.empty[Symbol] - private val paramsToSkip = MutSet[Symbol]() + private val paramsToSkip = mut.Set.empty[Symbol] + val exclude = Stack(ListBuffer.empty[Tree]) 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 = NoPrefix, includeForImport: Boolean = true, tree: Tree)(using Context): Unit = + if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) && !doNotRegisterPrefix(prefix.typeSymbol) + && !exclude.top.exists(_ eq tree) + 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 +410,32 @@ 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 + && !doNotRegisterPrefix(imp.expr.tpe.typeSymbol) + && 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,151 +443,134 @@ 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) + exclude.push(ListBuffer.empty) 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() + exclude.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 + def forgiven(sym: Symbol) = + containsSyntheticSuffix(sym) + || sym.owner.hasAnnotation(defn.UnusedAnnot) + || sym.info.isSingleton + for d <- explicitParamInScope do + if !d.symbol.usedDefContains && !isUsedInPosition(d.symbol.name, d.span) && !forgiven(d.symbol) then + warnings.addOne(UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)) + + if ctx.settings.WunusedHas.implicits then + def forgiven(sym: Symbol) = + val dd = defn + sym.name.is(ContextFunctionParamName) + || sym.owner.hasAnnotation(defn.UnusedAnnot) + || sym.info.typeSymbol.match + case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true + case _ => false + || sym.info.isSingleton + for d <- implicitParamInScope do + if !d.symbol.usedDefContains && !forgiven(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("$") + val mangled = symbol.name.mangledString + val index = mangled.indexOf('$') + index >= 0 && !(index == mangled.length - 1 && symbol.is(Module)) /** * Is the constructor of synthetic package object @@ -646,110 +597,84 @@ 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` + /** Avoid reporting unused parameter in required main method signature. + * The method may be user-written or generated by `@main`. */ private def isSyntheticMainParam(sym: Symbol)(using Context): Boolean = - sym.exists && ctx.platform.isMainMethod(sym.owner) && sym.owner.is(Synthetic) + 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, and ignore certain other trees. */ - private def isDefIgnored(memDef: tpd.MemberDef)(using Context): Boolean = - memDef.symbol.isOneOf(GivenOrImplicit) && memDef.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) + private def isDefIgnored(tree: Tree)(using Context): Boolean = + exclude.top.exists(t => t.find(_ eq tree).isDefined) || derivesFromCanEqual(tree.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) + && (prefix.eq(NoPrefix) || 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 + (prefix.eq(NoPrefix) || 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.is(Accessor) + } private def usedDefContains(using Context): Boolean = sym.everySymbol.exists(usedDef.apply) @@ -757,40 +682,41 @@ 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 => // Detect Scala 2 SingleType - tp.underlying.classSymbol.is(Flags.Module) + tp.underlying.classSymbol.is(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) + && !memDef.symbol.owner.isRefinementClass private def isValidParam(using Context): Boolean = val sym = memDef.symbol @@ -799,20 +725,25 @@ 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) + val sym = memDef.symbol + peekScopeType == ScopeType.Template + && !sym.isConstructor + && (sym.is(Private, butNot = SelfName | Synthetic | CaseAccessor) + || sym.effectiveOwner.is(PackageClass) && sym.denot.privateWithin == sym.effectiveOwner) + && !ignoredSignature(sym) 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 +756,19 @@ 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", "readObjectNoData", "writeObject", "writeReplace").map(termName(_)) + + def ignoredSignature(m: Symbol)(using Context): Boolean = + m.is(Method) && ignoredNames(m.name.toTermName) && m.owner.isClass + && m.owner.asClass.classDenot.parentSyms.contains(defn.JavaSerializableClass) + + final case class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): private var myUsed: Boolean = false def markUsed(): Unit = myUsed = true @@ -845,25 +783,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/pos/i17631.scala b/tests/pos/i17631.scala index 7b8a064493df..ddcb71354968 100644 --- a/tests/pos/i17631.scala +++ b/tests/pos/i17631.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Werror -Wunused:all -deprecation -feature object foo { type Bar @@ -32,3 +32,23 @@ object Main { (bad1, bad2) } } + +def `i18388`: Unit = + def func(pred: [A] => A => Boolean): Unit = + val _ = pred + () + val _ = func + +trait L[T]: + type E + +def `i19748` = + type Warn1 = [T] => (l: L[T]) => T => l.E + type Warn2 = [T] => L[T] => T + type Warn3 = [T] => T => T + def use(x: (Warn1, Warn2, Warn3)) = x + use + +type NoWarning1 = [T] => (l: L[T]) => T => l.E +type NoWarning2 = [T] => L[T] => T +type NoWarning3 = [T] => T => T diff --git a/tests/pos/i18366.scala b/tests/pos/i18366.scala index 698510ad13a2..b6385b5bbb59 100644 --- a/tests/pos/i18366.scala +++ b/tests/pos/i18366.scala @@ -1,10 +1,19 @@ -//> using options -Xfatal-warnings -Wunused:all +//> using options -Werror -Wunused:all trait Builder { def foo(): Unit } -def repro = +def `i18366` = val builder: Builder = ??? import builder.{foo => bar} - bar() \ No newline at end of file + bar() + +import java.io.DataOutputStream + +val buffer: DataOutputStream = ??? + +import buffer.{write => put} + +def `i17315` = + put(0: Byte) diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 26221899035b..94dc55ca165e 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -575,7 +575,7 @@ Text => empty Language => Scala Symbols => 108 entries Occurrences => 127 entries -Diagnostics => 6 entries +Diagnostics => 10 entries Synthetics => 2 entries Symbols: @@ -830,6 +830,10 @@ See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifie This construct can be rewritten automatically under -rewrite -source 3.4-migration. [22:27..22:28): [warning] unused explicit parameter [24:10..24:11): [warning] unused explicit parameter +[36:11..36:12): [warning] unused explicit parameter +[39:11..39:12): [warning] unused explicit parameter +[39:19..39:20): [warning] unused explicit parameter +[51:30..51:31): [warning] unused explicit parameter Synthetics: [51:16..51:27):List(1).map => *[Int] @@ -3533,6 +3537,7 @@ Text => empty Language => Scala Symbols => 62 entries Occurrences => 165 entries +Diagnostics => 2 entries Synthetics => 39 entries Symbols: @@ -3766,6 +3771,10 @@ Occurrences: [68:18..68:24): impure -> local20 [68:30..68:31): s -> local16 +Diagnostics: +[19:21..19:22): [warning] unused explicit parameter +[41:4..41:5): [warning] unused explicit parameter + Synthetics: [5:2..5:13):List(1).map => *[Int] [5:2..5:6):List => *.apply[Int] @@ -4235,6 +4244,7 @@ Text => empty Language => Scala Symbols => 6 entries Occurrences => 11 entries +Diagnostics => 2 entries Symbols: example/Vararg# => class Vararg extends Object { self: Vararg => +3 decls } @@ -4257,6 +4267,10 @@ Occurrences: [4:18..4:21): Int -> scala/Int# [4:26..4:30): Unit -> scala/Unit# +Diagnostics: +[3:11..3:12): [warning] unused explicit parameter +[4:11..4:12): [warning] unused explicit parameter + expect/dep-match.scala ---------------------- @@ -4873,6 +4887,7 @@ Text => empty Language => Scala Symbols => 36 entries Occurrences => 48 entries +Diagnostics => 5 entries Symbols: local0 => type N$1 <: Nat @@ -4962,6 +4977,13 @@ Occurrences: [23:35..23:39): Zero -> recursion/Nats.Zero. [23:40..23:42): ++ -> recursion/Nats.Nat#`++`(). +Diagnostics: +[10:24..10:24): [warning] unused local definition +[10:33..11:5): [warning] unused local definition +[23:19..23:19): [warning] unused local definition +[23:24..23:32): [warning] unused local definition +[23:40..25:2): [warning] unused local definition + expect/semanticdb-Definitions.scala ----------------------------------- @@ -5006,7 +5028,7 @@ Text => empty Language => Scala Symbols => 50 entries Occurrences => 78 entries -Diagnostics => 4 entries +Diagnostics => 5 entries Synthetics => 2 entries Symbols: @@ -5142,6 +5164,7 @@ Occurrences: [25:33..25:36): ??? -> scala/Predef.`???`(). Diagnostics: +[5:19..5:20): [warning] unused private member [9:30..9:31): [warning] unused explicit parameter [9:36..9:37): [warning] unused explicit parameter [9:42..9:43): [warning] unused explicit parameter @@ -5161,7 +5184,7 @@ Text => empty Language => Scala Symbols => 143 entries Occurrences => 246 entries -Diagnostics => 4 entries +Diagnostics => 5 entries Synthetics => 1 entries Symbols: @@ -5565,6 +5588,7 @@ This construct can be rewritten automatically under -rewrite -source 3.4-migrati This construct can be rewritten automatically under -rewrite -source 3.4-migration. [71:31..71:31): [warning] `_` is deprecated for wildcard arguments of types: use `?` instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. +[96:13..96:14): [warning] unused explicit parameter Synthetics: [68:20..68:24):@ann => *[Int] 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/i15503b.scala b/tests/warn/i15503b.scala index 7ab86026ff00..0ea110f833f1 100644 --- a/tests/warn/i15503b.scala +++ b/tests/warn/i15503b.scala @@ -117,7 +117,7 @@ package foo.scala2.tests: object Types { def l1() = { - object HiObject { def f = this } // OK + object HiObject { def f = this } // warn class Hi { // warn def f1: Hi = new Hi def f2(x: Hi) = x @@ -141,4 +141,4 @@ package test.foo.twisted.i16682: } isInt - def f = myPackage("42") \ No newline at end of file + def f = myPackage("42") diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index 46d73a4945cd..01d4ddd266d6 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:explicits +//> using options -Wunused:explicits object Foo { /* This goes around the "trivial method" detection */ @@ -31,7 +31,7 @@ package scala3main: package foo.test.lambda.param: val default_val = 1 val a = (i: Int) => i // OK - val b = (i: Int) => default_val // OK + val b = (i: Int) => default_val // warn val c = (_: Int) => default_val // OK package foo.test.trivial: @@ -64,7 +64,7 @@ package foo.test.i16865: trait Bar extends Foo object Ex extends Bar: - def fn(a: Int, b: Int): Int = b + 3 // OK + def fn(a: Int, b: Int): Int = b + 3 // warn object Ex2 extends Bar: - override def fn(a: Int, b: Int): Int = b + 3 // OK \ No newline at end of file + override def fn(a: Int, b: Int): Int = b + 3 // warn diff --git a/tests/warn/i15503f.scala b/tests/warn/i15503f.scala index ccf0b7e74065..4656771980ae 100644 --- a/tests/warn/i15503f.scala +++ b/tests/warn/i15503f.scala @@ -6,8 +6,8 @@ val default_int = 1 object Xd { private def f1(a: Int) = a // OK private def f2(a: Int) = 1 // OK - private def f3(a: Int)(using Int) = a // OK - private def f4(a: Int)(using Int) = default_int // OK + private def f3(a: Int)(using Int) = a // warn + private def f4(a: Int)(using Int) = default_int // warn private def f6(a: Int)(using Int) = summon[Int] // OK private def f7(a: Int)(using Int) = summon[Int] + a // OK private def f8(a: Int)(using foo: Int) = a // warn diff --git a/tests/warn/i15503g.scala b/tests/warn/i15503g.scala index fbd9f3c1352c..13f8b56b8654 100644 --- a/tests/warn/i15503g.scala +++ b/tests/warn/i15503g.scala @@ -6,8 +6,8 @@ object Foo { private def f1(a: Int) = a // OK private def f2(a: Int) = default_int // warn - private def f3(a: Int)(using Int) = a // OK - private def f4(a: Int)(using Int) = default_int // warn + private def f3(a: Int)(using Int) = a // warn + private def f4(a: Int)(using Int) = default_int // warn // warn private def f6(a: Int)(using Int) = summon[Int] // warn private def f7(a: Int)(using Int) = summon[Int] + a // OK /* --- Trivial method check --- */ @@ -20,4 +20,4 @@ package foo.test.i17101: extension[A] (x: Test[A]) { // OK def value: A = x def causesIssue: Unit = println("oh no") - } \ No newline at end of file + } diff --git a/tests/warn/i15503h.scala b/tests/warn/i15503h.scala index 854693981488..a2bf47c843dd 100644 --- a/tests/warn/i15503h.scala +++ b/tests/warn/i15503h.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:linted +//> using options -Wunused:linted import collection.mutable.Set // warn @@ -7,7 +7,7 @@ class A { val b = 2 // OK private def c = 2 // warn - def d(using x:Int): Int = b // ok + def d(using x: Int): Int = b // warn def e(x: Int) = 1 // OK def f = val x = 1 // warn @@ -15,6 +15,6 @@ class A { 3 def g(x: Int): Int = x match - case x:1 => 0 // OK + case x: 1 => 0 // OK case _ => 1 -} \ No newline at end of file +} diff --git a/tests/warn/i15503i.scala b/tests/warn/i15503i.scala index b7981e0e4206..257447704cfb 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 @@ -17,10 +17,12 @@ class A { private def c2 = 2 // OK def c3 = c2 - def d1(using x:Int): Int = default_int // ok - def d2(using x:Int): Int = x // OK + def d1(using x: Int): Int = default_int // warn param + def d2(using x: Int): Int = x // OK + def d3(using Int): Int = summon[Int] // OK + def d4(using Int): Int = default_int // warn - def e1(x: Int) = default_int // ok + def e1(x: Int) = default_int // warn param def e2(x: Int) = x // OK def f = val x = 1 // warn @@ -44,7 +46,7 @@ package foo.test.scala.annotation: val default_int = 12 def a1(a: Int) = a // OK - def a2(a: Int) = default_int // ok + def a2(a: Int) = default_int // warn def a3(@unused a: Int) = default_int //OK @@ -82,8 +84,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 +249,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 +265,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 +281,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/i16639a.scala b/tests/warn/i16639a.scala index 9fe4efe57d7b..47dd431c62bf 100644 --- a/tests/warn/i16639a.scala +++ b/tests/warn/i16639a.scala @@ -91,7 +91,7 @@ trait Locals { } object Types { - private object Dongo { def f = this } // no more warn since #17061 + private object Dongo { def f = this } // warn private class Bar1 // warn warn private class Bar2 // no warn private type Alias1 = String // warn warn @@ -101,7 +101,7 @@ object Types { def f(x: Alias2) = x.length def l1() = { - object HiObject { def f = this } // no more warn since #17061 + object HiObject { def f = this } // warn class Hi { // warn warn def f1: Hi = new Hi def f2(x: Hi) = x @@ -202,4 +202,4 @@ trait `short comings` { val x = 42 // warn /Dotty only triggers in dotty 17 } -} \ No newline at end of file +} diff --git a/tests/pos/i17314.scala b/tests/warn/i17314.scala similarity index 84% rename from tests/pos/i17314.scala rename to tests/warn/i17314.scala index 8ece4a3bd7ac..cff90d843c38 100644 --- a/tests/pos/i17314.scala +++ b/tests/warn/i17314.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Wunused:all -deprecation -feature import java.net.URI @@ -10,9 +10,7 @@ object circelike { type Configuration trait ConfiguredCodec[T] object ConfiguredCodec: - inline final def derived[A](using conf: Configuration)(using - inline mirror: Mirror.Of[A] - ): ConfiguredCodec[A] = + inline final def derived[A](using conf: Configuration)(using inline mirror: Mirror.Of[A]): ConfiguredCodec[A] = // warn // warn class InlinedConfiguredCodec extends ConfiguredCodec[A]: val codec = summonInline[Codec[URI]] // simplification new InlinedConfiguredCodec diff --git a/tests/pos/i17314a.scala b/tests/warn/i17314a.scala similarity index 70% rename from tests/pos/i17314a.scala rename to tests/warn/i17314a.scala index 4bce56d8bbed..4593fc92274d 100644 --- a/tests/pos/i17314a.scala +++ b/tests/warn/i17314a.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Wunused:all -deprecation -feature package foo: class Foo[T] diff --git a/tests/warn/i17314b.scala b/tests/warn/i17314b.scala index e1500028ca93..ad4c8f1e4a31 100644 --- a/tests/warn/i17314b.scala +++ b/tests/warn/i17314b.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all +//> using options -Wunused:all package foo: class Foo[T] diff --git a/tests/warn/i17371.scala b/tests/warn/i17371.scala new file mode 100644 index 000000000000..c7e321882f62 --- /dev/null +++ b/tests/warn/i17371.scala @@ -0,0 +1,28 @@ +//> using options -Wunused:all + +class A +class B + +def Test() = + val ordA: Ordering[A] = ??? + val ordB: Ordering[B] = ??? + val a: A = ??? + val b: B = ??? + + import ordA.given + val _ = a > a + + import ordB.given + val _ = b < b + +// unminimized OP +trait Circular[T] extends Ordering[T] +trait Turns[C: Circular, T] extends Ordering[T]: // warn Circular is not a marker interface + extension (turns: T) def extract: C + +def f[K, T](start: T, end: T)(using circular: Circular[K], turns: Turns[K, T]): Boolean = + import turns.given + if start > end then throw new IllegalArgumentException("start must be <= end") + + import circular.given + start.extract < end.extract diff --git a/tests/warn/i18313.scala b/tests/warn/i18313.scala new file mode 100644 index 000000000000..967985853fcf --- /dev/null +++ b/tests/warn/i18313.scala @@ -0,0 +1,14 @@ +//> using options -Wunused:imports + +import scala.deriving.Mirror + +case class Test(i: Int, d: Double) +case class Decoder(d: Product => Test) + +// OK, no warning returned +//val ok = Decoder(summon[Mirror.Of[Test]].fromProduct) +// +// returns warning: +// [warn] unused import +// [warn] import scala.deriving.Mirror +val d = Decoder(d = summon[Mirror.Of[Test]].fromProduct) // no warn 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..06297cd60d5c --- /dev/null +++ b/tests/warn/i19657.scala @@ -0,0 +1,115 @@ +//> 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 + +import scala.annotation.meta.* +object Alias { + type A = Deprecated @param +} + +// avoid reporting on runtime (nothing to do with transparent inline) +import scala.runtime.EnumValue + +trait Lime + +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) with EnumValue + case Green extends Color(0x00FF00) with Lime + case Blue extends Color(0x0000FF) + +object prefixes: + class C: + object N: + type U + object Test: + val c: C = ??? + def k2: c.N.U = ??? + import c.N.* + def k3: U = ??? // TypeTree if not a select + object Alt: + val c: C = ??? + import c.N + def k4: N.U = ??? +end prefixes 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/scala2-t11681.scala b/tests/warn/scala2-t11681.scala index ae2187181ceb..bddd18855f2e 100644 --- a/tests/warn/scala2-t11681.scala +++ b/tests/warn/scala2-t11681.scala @@ -23,7 +23,7 @@ trait BadAPI extends InterFace { a } override def call(a: Int, - b: String, // OK + b: String, // warn now c: Double): Int = { println(c) a @@ -33,7 +33,7 @@ trait BadAPI extends InterFace { override def equals(other: Any): Boolean = true // OK - def i(implicit s: String) = answer // ok + def i(implicit s: String) = answer // warn now /* def future(x: Int): Int = { @@ -62,7 +62,7 @@ case class CaseyKasem(k: Int) // OK case class CaseyAtTheBat(k: Int)(s: String) // ok trait Ignorance { - def f(readResolve: Int) = answer // ok + def f(readResolve: Int) = answer // warn now } class Reusing(u: Int) extends Unusing(u) // OK @@ -78,30 +78,30 @@ trait Unimplementation { trait DumbStuff { def f(implicit dummy: DummyImplicit) = answer // ok - def g(dummy: DummyImplicit) = answer // ok + def g(dummy: DummyImplicit) = answer // warn now } trait Proofs { def f[A, B](implicit ev: A =:= B) = answer // ok def g[A, B](implicit ev: A <:< B) = answer // ok - def f2[A, B](ev: A =:= B) = answer // ok - def g2[A, B](ev: A <:< B) = answer // ok + def f2[A, B](ev: A =:= B) = answer // warn now + def g2[A, B](ev: A <:< B) = answer // warn now } trait Anonymous { - def f = (i: Int) => answer // ok + def f = (i: Int) => answer // warn now def f1 = (_: Int) => answer // OK def f2: Int => Int = _ + 1 // OK - def g = for (i <- List(1)) yield answer // ok + def g = for (i <- List(1)) yield answer // warn } trait Context[A] trait Implicits { - def f[A](implicit ctx: Context[A]) = answer // ok - def g[A: Context] = answer // OK + def f[A](implicit ctx: Context[A]) = answer // warn + def g[A: Context] = answer // warn } -class Bound[A: Context] // OK +class Bound[A: Context] // warn object Answers { def answer: Int = 42 } diff --git a/tests/warn/unused-locals.scala b/tests/warn/unused-locals.scala new file mode 100644 index 000000000000..10bf160fb717 --- /dev/null +++ b/tests/warn/unused-locals.scala @@ -0,0 +1,43 @@ +//> using options -Wunused:locals + +class Outer { + class Inner +} + +trait Locals { + def f0 = { + var x = 1 // warn + var y = 2 // no warn + 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 { + 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 + } +} + +// breakage: local val x$1 in method skolemize is never used +case class SymbolKind(accurate: String, sanitized: String, abbreviation: String) { + def skolemize: SymbolKind = copy(accurate = s"$accurate skolem", abbreviation = s"$abbreviation#SKO") +} diff --git a/tests/warn/unused-params.scala b/tests/warn/unused-params.scala new file mode 100644 index 000000000000..e157764b62dc --- /dev/null +++ b/tests/warn/unused-params.scala @@ -0,0 +1,153 @@ +//> using options -Wunused:params +// + +import Answers._ + +trait InterFace { + /** Call something. */ + def call(a: Int, b: String, c: Double): Int +} + +trait BadAPI extends InterFace { + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = answer // warn + + /* + def future(x: Int): Int = { + val y = 42 + val x = y // maybe option to warn only if shadowed + x + } + */ +} + +// mustn't alter warnings in super +trait PoorClient extends BadAPI { + override def meth(x: Int) = ??? // no warn + override def f(a: Int, b: String, c: Double): Int = a + b.toInt + c.toInt +} + +class Unusing(u: Int) { // warn + def f = ??? +} + +class Valuing(val u: Int) // no warn + +class Revaluing(u: Int) { def f = u } // no warn + +case class CaseyKasem(k: Int) // no warn + +case class CaseyAtTheBat(k: Int)(s: String) // NO warn + +trait Ignorance { + def f(readResolve: Int) = answer // warn +} + +class Reusing(u: Int) extends Unusing(u) // no warn + +class Main { + def main(args: Array[String]): Unit = println("hello, args") // no warn +} + +trait Unimplementation { + def f(u: Int): Int = ??? // no warn for param in unimplementation +} + +trait DumbStuff { + def f(implicit dummy: DummyImplicit) = answer + def g(dummy: DummyImplicit) = answer // warn +} +trait Proofs { + def f[A, B](implicit ev: A =:= B) = answer + def g[A, B](implicit ev: A <:< B) = answer + def f2[A, B](ev: A =:= B) = answer // warn + def g2[A, B](ev: A <:< B) = answer // warn +} + +trait Anonymous { + def f = (i: Int) => answer // warn + + def f1 = (_: Int) => answer // no warn underscore parameter (a fresh name) + + def f2: Int => Int = _ + 1 // no warn placeholder syntax (a fresh name and synthetic parameter) + + def g = for (i <- List(1)) yield answer // warn // TODO no warn patvar elaborated as map.(i => 42) +} +trait Context[A] { def m(a: A): A = a } +trait Implicits { + def f[A](implicit ctx: Context[A]) = answer // warn + def g[A: Context] = answer // warn +} +class Bound[A: Context] // warn +object Answers { + def answer: Int = 42 +} + +trait BadMix { self: InterFace => + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + XXXX: String, // warn no longer excused because required by superclass + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = answer // warn +} + +class Unequal { + override def equals(other: Any) = toString.nonEmpty // warn +} + +class Seriously { + def f(s: Serializable) = toString.nonEmpty // warn explicit param of marker trait +} + +class TryStart(start: String) { + def FINALLY(end: END.type) = start // no warn for DSL taking a singleton +} + +object END + +class Nested { + @annotation.unused private def actuallyNotUsed(fresh: Int, stale: Int) = fresh // no warn if owner is unused +} diff --git a/tests/warn/unused-privates.scala b/tests/warn/unused-privates.scala new file mode 100644 index 000000000000..963fa3093e48 --- /dev/null +++ b/tests/warn/unused-privates.scala @@ -0,0 +1,306 @@ +// +//> 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 } // 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 + } +} + +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 // warn wrong signatured for 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() + } +} + +class `absolve serial framework` extends Serializable: + import java.io.{IOException, ObjectInputStream, ObjectOutputStream, ObjectStreamException} + @throws(classOf[IOException]) + private def writeObject(stream: ObjectOutputStream): Unit = () + @throws(classOf[ObjectStreamException]) + private def writeReplace(): Object = ??? + @throws(classOf[ClassNotFoundException]) + @throws(classOf[IOException]) + private def readObject(stream: ObjectInputStream): Unit = () + @throws(classOf[ObjectStreamException]) + private def readObjectNoData(): Unit = () + @throws(classOf[ObjectStreamException]) + private def readResolve(): Object = ??? + +class `absolve ONLY serial framework`: + import java.io.{IOException, ObjectInputStream, ObjectOutputStream, ObjectStreamException} + @throws(classOf[IOException]) + private def writeObject(stream: ObjectOutputStream): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def writeReplace(): Object = ??? // warn + @throws(classOf[ClassNotFoundException]) + @throws(classOf[IOException]) + private def readObject(stream: ObjectInputStream): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def readObjectNoData(): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def readResolve(): Object = ??? // warn + +@throws(classOf[java.io.ObjectStreamException]) +private def readResolve(): Object = ??? // warn +private def print() = println() // warn +private val printed = false // warn + +package locked: + private[locked] def locker(): Unit = () // warn as we cannot distinguish unqualified private at top level + package basement: + private[locked] def shackle(): Unit = () // no warn as it is not top level at boundary + +object `i19998 refinement`: + trait Foo { + type X[a] + } + trait Bar[X[_]] { + private final type SelfX[a] = X[a] // was false positive + val foo: Foo { type X[a] = SelfX[a] } + }