diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index b121a47781e1..375cdaaa2e94 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -35,6 +35,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) :: // Check for unused elements List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index 68c900e405da..914df040fbf7 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -60,7 +60,7 @@ trait CliCommand: def defaultValue = s.default match case _: Int | _: String => s.default.toString case _ => "" - val info = List(shortHelp(s), if defaultValue.nonEmpty then s"Default $defaultValue" else "", if s.legalChoices.nonEmpty then s"Choices ${s.legalChoices}" else "") + val info = List(shortHelp(s), if defaultValue.nonEmpty then s"Default $defaultValue" else "", if s.legalChoices.nonEmpty then s"Choices : ${s.legalChoices}" else "") (s.name, info.filter(_.nonEmpty).mkString("\n")) end help diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 64b8a91e9096..24fc19a46b2c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -155,20 +155,70 @@ private sealed trait VerboseSettings: */ private sealed trait WarningSettings: self: SettingGroup => + import Setting.ChoiceWithHelp + val Whelp: Setting[Boolean] = BooleanSetting("-W", "Print a synopsis of warning options.") val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) - val Wunused: Setting[List[String]] = MultiChoiceSetting( + val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting( name = "-Wunused", helpArg = "warning", descr = "Enable or disable specific `unused` warnings", - choices = List("nowarn", "all"), + choices = List( + ChoiceWithHelp("nowarn", ""), + ChoiceWithHelp("all",""), + ChoiceWithHelp( + name = "imports", + description = "Warn if an import selector is not referenced.\n" + + "NOTE : overrided by -Wunused:strict-no-implicit-warn"), + ChoiceWithHelp("privates","Warn if a private member is unused"), + ChoiceWithHelp("locals","Warn if a local definition is unused"), + ChoiceWithHelp("explicits","Warn if an explicit parameter is unused"), + ChoiceWithHelp("implicits","Warn if an implicit parameter is unused"), + ChoiceWithHelp("params","Enable -Wunused:explicits,implicits"), + ChoiceWithHelp("linted","Enable -Wunused:imports,privates,locals,implicits"), + ChoiceWithHelp( + name = "strict-no-implicit-warn", + description = "Same as -Wunused:import, only for imports of explicit named members.\n" + + "NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all" + ), + // ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"), + ChoiceWithHelp( + name = "unsafe-warn-patvars", + description = "(UNSAFE) Warn if a variable bound in a pattern is unused.\n" + + "This warning can generate false positive, as warning cannot be\n" + + "suppressed yet." + ) + ), default = Nil ) object WunusedHas: + def isChoiceSet(s: String)(using Context) = Wunused.value.pipe(us => us.contains(s)) def allOr(s: String)(using Context) = Wunused.value.pipe(us => us.contains("all") || us.contains(s)) def nowarn(using Context) = allOr("nowarn") + // overrided by strict-no-implicit-warn + def imports(using Context) = + (allOr("imports") || allOr("linted")) && !(strictNoImplicitWarn) + def locals(using Context) = + allOr("locals") || allOr("linted") + /** -Wunused:explicits OR -Wunused:params */ + def explicits(using Context) = + allOr("explicits") || allOr("params") + /** -Wunused:implicits OR -Wunused:params */ + def implicits(using Context) = + allOr("implicits") || allOr("params") || allOr("linted") + def params(using Context) = allOr("params") + def privates(using Context) = + allOr("privates") || allOr("linted") + def patvars(using Context) = + isChoiceSet("unsafe-warn-patvars") // not with "all" + // allOr("patvars") // todo : rename once fixed + def linted(using Context) = + allOr("linted") + def strictNoImplicitWarn(using Context) = + isChoiceSet("strict-no-implicit-warn") + val Wconf: Setting[List[String]] = MultiStringSetting( "-Wconf", "patterns", diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 084d79ed4556..34e5582e8a91 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -11,6 +11,7 @@ import annotation.tailrec import collection.mutable.ArrayBuffer import reflect.ClassTag import scala.util.{Success, Failure} +import dotty.tools.dotc.config.Settings.Setting.ChoiceWithHelp object Settings: @@ -189,6 +190,19 @@ object Settings: def update(x: T)(using Context): SettingsState = setting.updateIn(ctx.settingsState, x) def isDefault(using Context): Boolean = setting.isDefaultIn(ctx.settingsState) + /** + * A choice with help description. + * + * NOTE : `equals` and `toString` have special behaviors + */ + case class ChoiceWithHelp[T](name: T, description: String): + override def equals(x: Any): Boolean = x match + case s:String => s == name.toString() + case _ => false + override def toString(): String = + s"\n- $name${if description.isEmpty() then "" else s" :\n\t${description.replace("\n","\n\t")}"}" + end Setting + class SettingGroup { private val _allSettings = new ArrayBuffer[Setting[?]] @@ -270,6 +284,9 @@ object Settings: def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: List[String], aliases: List[String] = Nil): Setting[List[String]] = publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases)) + def MultiChoiceHelpSetting(name: String, helpArg: String, descr: String, choices: List[ChoiceWithHelp[String]], default: List[ChoiceWithHelp[String]], aliases: List[String] = Nil): Setting[List[ChoiceWithHelp[String]]] = + publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases)) + def IntSetting(name: String, descr: String, default: Int, aliases: List[String] = Nil): Setting[Int] = publish(Setting(name, descr, default, aliases = aliases)) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 6d766e8fcc73..81a9d4db2e40 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1001,6 +1001,7 @@ class Definitions { @tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative") @tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration") @tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn") + @tu lazy val UnusedAnnot: ClassSymbol = requiredClass("scala.annotation.unused") @tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait") @tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native") @tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated") diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala new file mode 100644 index 000000000000..5549e5c48d45 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -0,0 +1,615 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.tpd.TreeTraverser +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.ast.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.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.typer.ImportInfo +import dotty.tools.dotc.util.Property +import dotty.tools.dotc.core.Mode +import dotty.tools.dotc.core.Types.TypeTraverser +import dotty.tools.dotc.core.Types.Type +import dotty.tools.dotc.core.Types.AnnotatedType +import dotty.tools.dotc.core.Flags.flagsString +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.core.Annotations +import dotty.tools.dotc.core.Definitions +import dotty.tools.dotc.core.Types.ConstantType +import dotty.tools.dotc.core.NameKinds.WildcardParamName +import dotty.tools.dotc.core.Types.TermRef + + + +/** + * 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. + */ +class CheckUnused extends MiniPhase: + import CheckUnused.UnusedData + + /** + * The key used to retrieve the "unused entity" analysis metadata, + * from the compilation `Context` + */ + private val _key = Property.Key[UnusedData] + + extension (k: Property.Key[UnusedData]) + private def unusedDataApply[U](f: UnusedData => U)(using Context): Context = + ctx.property(_key).foreach(f) + ctx + private def getUnusedData(using Context): Option[UnusedData] = + ctx.property(_key) + + override def phaseName: String = CheckUnused.phaseName + + override def description: String = CheckUnused.description + + override def isRunnable(using Context): Boolean = + ctx.settings.Wunused.value.nonEmpty && + !ctx.isJava + + // ========== SETUP ============ + + override def prepareForUnit(tree: tpd.Tree)(using Context): Context = + val data = UnusedData() + val fresh = ctx.fresh.setProperty(_key, data) + fresh + + // ========== END + REPORTING ========== + + override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = + _key.unusedDataApply(ud => reportUnused(ud.getUnused)) + 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 prepareForIdent(tree: tpd.Ident)(using Context): Context = + _key.unusedDataApply(_.registerUsed(tree.symbol, Some(tree.name))) + + override def prepareForSelect(tree: tpd.Select)(using Context): Context = + _key.unusedDataApply(_.registerUsed(tree.symbol, Some(tree.name))) + + override def prepareForBlock(tree: tpd.Block)(using Context): Context = + pushInBlockTemplatePackageDef(tree) + + override def prepareForTemplate(tree: tpd.Template)(using Context): Context = + pushInBlockTemplatePackageDef(tree) + + override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = + pushInBlockTemplatePackageDef(tree) + + override def prepareForValDef(tree: tpd.ValDef)(using Context): Context = + _key.unusedDataApply{ud => + ud.registerDef(tree) + ud.addIgnoredUsage(tree.symbol) + } + + override def prepareForDefDef(tree: tpd.DefDef)(using Context): Context = + _key.unusedDataApply{ ud => + import ud.registerTrivial + tree.registerTrivial + ud.registerDef(tree) + ud.addIgnoredUsage(tree.symbol) + } + + override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = + _key.unusedDataApply{ ud => + if !tree.symbol.is(Param) then // Ignore type parameter (as Scala 2) + ud.registerDef(tree) + ud.addIgnoredUsage(tree.symbol) + } + + override def prepareForBind(tree: tpd.Bind)(using Context): Context = + _key.unusedDataApply(_.registerPatVar(tree)) + + override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context = + typeTraverser(_key.unusedDataApply).traverse(tree.tpe) + ctx + + // ========== MiniPhase Transform ========== + + override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree = + popOutBlockTemplatePackageDef() + tree + + override def transformTemplate(tree: tpd.Template)(using Context): tpd.Tree = + popOutBlockTemplatePackageDef() + tree + + override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = + popOutBlockTemplatePackageDef() + tree + + override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree = + _key.unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + tree + + override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree = + _key.unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + tree + + override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = + _key.unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + tree + + // ---------- MiniPhase HELPERS ----------- + + private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = + _key.unusedDataApply { ud => + ud.pushScope(UnusedData.ScopeType.fromTree(tree)) + } + ctx + + private def popOutBlockTemplatePackageDef()(using Context): Context = + _key.unusedDataApply { ud => + ud.popScope() + } + ctx + + private def newCtx(tree: tpd.Tree)(using Context) = + if tree.symbol.exists then ctx.withOwner(tree.symbol) else 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 => + _key.unusedDataApply(_.registerImport(imp)) + traverseChildren(tree)(using newCtx) + case ident: Ident => + prepareForIdent(ident) + traverseChildren(tree)(using newCtx) + case sel: Select => + prepareForSelect(sel) + traverseChildren(tree)(using newCtx) + case _: (tpd.Block | tpd.Template | tpd.PackageDef) => + //! DIFFERS FROM MINIPHASE + _key.unusedDataApply { ud => + ud.inNewScope(ScopeType.fromTree(tree))(traverseChildren(tree)(using newCtx)) + } + case t:tpd.ValDef => + prepareForValDef(t) + traverseChildren(tree)(using newCtx) + case t:tpd.DefDef => + prepareForDefDef(t) + traverseChildren(tree)(using newCtx) + case t:tpd.TypeDef => + prepareForTypeDef(t) + traverseChildren(tree)(using newCtx) + case t: tpd.Bind => + prepareForBind(t) + traverseChildren(tree)(using newCtx) + case t@tpd.TypeTree() => + //! DIFFERS FROM MINIPHASE + typeTraverser(_key.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 = tp match + case AnnotatedType(_, annot) => dt(_.registerUsed(annot.symbol, None)) + case _ => traverseChildren(tp) + + /** Do the actual reporting given the result of the anaylsis */ + private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit = + import CheckUnused.WarnTypes + res.warnings.foreach { s => + s match + case (t, WarnTypes.Imports) => + report.warning(s"unused import", t) + case (t, WarnTypes.LocalDefs) => + report.warning(s"unused local definition", t) + case (t, WarnTypes.ExplicitParams) => + report.warning(s"unused explicit parameter", t) + case (t, WarnTypes.ImplicitParams) => + report.warning(s"unused implicit parameter", t) + case (t, WarnTypes.PrivateMembers) => + report.warning(s"unused private member", t) + case (t, WarnTypes.PatVars) => + report.warning(s"unused pattern variable", t) + } + +end CheckUnused + +object CheckUnused: + val phaseName: String = "checkUnused" + val description: String = "check for unused elements" + + private enum WarnTypes: + case Imports + case LocalDefs + case ExplicitParams + case ImplicitParams + case PrivateMembers + case PatVars + + /** + * A stateful class gathering the infos on : + * - imports + * - definitions + * - usage + */ + private class UnusedData: + import dotty.tools.dotc.transform.CheckUnused.UnusedData.UnusedResult + import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack} + import dotty.tools.dotc.core.Symbols.Symbol + import UnusedData.ScopeType + + /** The current scope during the tree traversal */ + var currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other) + + /* IMPORTS */ + private val impInScope = MutStack(MutSet[tpd.Import]()) + /** + * 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,Boolean, Option[Name])]()) + /* unused import collected during traversal */ + private val unusedImport = MutSet[ImportSelector]() + + /* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */ + private val localDefInScope = MutSet[tpd.MemberDef]() + private val privateDefInScope = MutSet[tpd.MemberDef]() + private val explicitParamInScope = MutSet[tpd.MemberDef]() + private val implicitParamInScope = MutSet[tpd.MemberDef]() + private val patVarsInScope = MutSet[tpd.Bind]() + + /* Unused collection collected at the end */ + private val unusedLocalDef = MutSet[tpd.MemberDef]() + private val unusedPrivateDef = MutSet[tpd.MemberDef]() + private val unusedExplicitParams = MutSet[tpd.MemberDef]() + private val unusedImplicitParams = MutSet[tpd.MemberDef]() + private val unusedPatVars = MutSet[tpd.Bind]() + + /** All used symbols */ + private val usedDef = MutSet[Symbol]() + /** Do not register as used */ + private val doNotRegister = MutSet[Symbol]() + + /** Trivial definitions, avoid registering params */ + private val trivialDefs = MutSet[Symbol]() + + /** + * Push a new Scope of the given type, executes the given Unit and + * pop it back to the original type. + */ + def inNewScope(newScope: ScopeType)(execInNewScope: => Unit)(using Context): Unit = + val prev = currScopeType + pushScope(newScope) + execInNewScope + popScope() + + /** Register all annotations of this symbol's denotation */ + def registerUsedAnnotation(sym: Symbol)(using Context): Unit = + val annotSym = sym.denot.annotations.map(_.symbol) + annotSym.foreach(s => registerUsed(s, None)) + + /** + * 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 + */ + def registerUsed(sym: Symbol, name: Option[Name])(using Context): Unit = + if !isConstructorOfSynth(sym) && !doNotRegister(sym) then + usedInScope.top += ((sym, sym.isAccessibleAsIdent, name)) + if sym.isConstructor && sym.exists then + registerUsed(sym.owner, None) // constructor are "implicitly" imported with the class + + /** Register a symbol that should be ignored */ + def addIgnoredUsage(sym: Symbol)(using Context): Unit = + doNotRegister += sym + if sym.is(Flags.Module) then + doNotRegister += sym.moduleClass + + /** Remove a symbol that shouldn't be ignored anymore */ + def removeIgnoredUsage(sym: Symbol)(using Context): Unit = + doNotRegister -= sym + if sym.is(Flags.Module) then + doNotRegister -= sym.moduleClass + + + /** Register an import */ + def registerImport(imp: tpd.Import)(using Context): Unit = + if !tpd.languageImport(imp.expr).nonEmpty then + impInScope.top += imp + unusedImport ++= imp.selectors.filter { s => + !shouldSelectorBeReported(imp, s) && !isImportExclusion(s) + } + + /** Register (or not) some `val` or `def` according to the context, scope and flags */ + def registerDef(memDef: tpd.MemberDef)(using Context): Unit = + // register the annotations for usage + registerUsedAnnotation(memDef.symbol) + if memDef.isValidMemberDef then + if memDef.isValidParam then + if memDef.symbol.isOneOf(GivenOrImplicit) then + implicitParamInScope += memDef + else + explicitParamInScope += memDef + else if currScopeType.top == ScopeType.Local then + localDefInScope += memDef + else if currScopeType.top == ScopeType.Template && memDef.symbol.is(Private, butNot = SelfName) then + privateDefInScope += memDef + + /** Register pattern variable */ + def registerPatVar(patvar: tpd.Bind)(using Context): Unit = + registerUsedAnnotation(patvar.symbol) + if !patvar.symbol.isUnusedAnnot then + patVarsInScope += patvar + + /** enter a new scope */ + def pushScope(newScopeType: ScopeType): Unit = + // unused imports : + currScopeType.push(newScopeType) + impInScope.push(MutSet()) + usedInScope.push(MutSet()) + + /** + * leave the current scope and do : + * + * - If there are imports in this scope check for unused ones + */ + def popScope()(using Context): Unit = + // used symbol in this scope + val used = usedInScope.pop().toSet + // used imports in this scope + val imports = impInScope.pop().toSet + val kept = used.filterNot { t => + val (sym, isAccessible, optName) = t + // keep the symbol for outer scope, if it matches **no** import + + // This is the first matching wildcard selector + var selWildCard: Option[ImportSelector] = None + + val exists = imports.exists { imp => + sym.isInImport(imp, isAccessible, optName) match + case None => false + case optSel@Some(sel) if sel.isWildcard => + if selWildCard.isEmpty then selWildCard = optSel + // We keep wildcard symbol for the end as they have the least precedence + false + case Some(sel) => + unusedImport -= sel + true + } + if !exists && selWildCard.isDefined then + unusedImport -= selWildCard.get + true // a matching import exists so the symbol won't be kept for outer scope + else + exists + } + // if there's an outer scope + if usedInScope.nonEmpty then + // we keep the symbols not referencing an import in this scope + // as it can be the only reference to an outer import + usedInScope.top ++= kept + // register usage in this scope for other warnings at the end of the phase + usedDef ++= used.map(_._1) + // retrieve previous scope type + currScopeType.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 + */ + def getUnused(using Context): UnusedResult = + popScope() + val sortedImp = + if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then + unusedImport.map(d => d.srcPos -> WarnTypes.Imports).toList + else + Nil + val sortedLocalDefs = + if ctx.settings.WunusedHas.locals then + localDefInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.LocalDefs).toList + else + Nil + val sortedExplicitParams = + if ctx.settings.WunusedHas.explicits then + explicitParamInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.ExplicitParams).toList + else + Nil + val sortedImplicitParams = + if ctx.settings.WunusedHas.implicits then + implicitParamInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.ImplicitParams).toList + else + Nil + val sortedPrivateDefs = + if ctx.settings.WunusedHas.privates then + privateDefInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.PrivateMembers).toList + else + Nil + val sortedPatVars = + if ctx.settings.WunusedHas.patvars then + patVarsInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.PatVars).toList + else + Nil + val warnings = List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s => + val pos = s._1.sourcePos + (pos.line, pos.column) + } + UnusedResult(warnings, Nil) + end getUnused + //============================ HELPERS ==================================== + + /** + * Is the the constructor of synthetic package object + * Should be ignored as it is always imported/used in package + * Trigger false negative on used import + * + * Without this check example: + * + * --- WITH PACKAGE : WRONG --- + * {{{ + * package a: + * val x: Int = 0 + * package b: + * import a._ // no warning + * }}} + * --- WITH OBJECT : OK --- + * {{{ + * object a: + * val x: Int = 0 + * object b: + * import a._ // unused warning + * }}} + */ + 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` + */ + private def isSyntheticMainParam(sym: Symbol)(using Context): Boolean = + sym.exists && ctx.platform.isMainMethod(sym.owner) && sym.owner.is(Synthetic) + + /** + * This is used to ignore exclusion imports (i.e. import `qual`.{`member` => _}) + */ + private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match + case untpd.Ident(name) => name == StdNames.nme.WILDCARD + case _ => false + + /** + * If -Wunused:strict-no-implicit-warn import and this import selector could potentially import implicit. + * return true + */ + private def shouldSelectorBeReported(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = + if ctx.settings.WunusedHas.strictNoImplicitWarn then + 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)) + else + false + + extension (sym: Symbol) + /** is accessible without import in current context */ + private def isAccessibleAsIdent(using Context): Boolean = + sym.exists && + 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 an option of selector that match import<->symbol */ + private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name])(using Context): Option[ImportSelector] = + val tpd.Import(qual, sels) = imp + val qualHasSymbol = qual.tpe.member(sym.name).alternatives.map(_.symbol).contains(sym) + def selector = sels.find(sel => (sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name) && symName.map(n => n.toTermName == sel.rename).getOrElse(true)) + def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven) || sym.is(Implicit))) + if qualHasSymbol && !isAccessible && sym.exists then + selector.orElse(wildcard) // selector with name or wildcard (or given) + else + None + + /** Annotated with @unused */ + private def isUnusedAnnot(using Context): Boolean = + sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot) + + private def shouldNotReportParamOwner(using Context): Boolean = + if sym.exists then + val owner = sym.owner + trivialDefs(owner) || + owner.is(Flags.Override) || + owner.isPrimaryConstructor || + owner.annotations.exists ( + _.symbol == ctx.definitions.DeprecatedAnnot + ) + else + false + + extension (defdef: tpd.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 + case _ => rhs.tpe match + case ConstantType(_) => true + case tp: TermRef => + // Detect Scala 2 SingleType + tp.underlying.classSymbol.is(Flags.Module) + case _ => + false + }) + def registerTrivial(using Context): Unit = + if defdef.isTrivial then + trivialDefs += defdef.symbol + + extension (memDef: tpd.MemberDef) + private def isValidMemberDef(using Context): Boolean = + !memDef.symbol.isUnusedAnnot && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) && !memDef.name.isWildcard + + private def isValidParam(using Context): Boolean = + val sym = memDef.symbol + (sym.is(Param) || sym.isAllOf(PrivateParamAccessor)) && + !isSyntheticMainParam(sym) && + !sym.shouldNotReportParamOwner + + + extension (thisName: Name) + private def isWildcard: Boolean = + thisName == StdNames.nme.WILDCARD || thisName.is(WildcardParamName) + + end UnusedData + + private object UnusedData: + enum ScopeType: + case Local + case Template + case Other + + object ScopeType: + /** return the scope corresponding to the enclosing scope of the given tree */ + def fromTree(tree: tpd.Tree): ScopeType = tree match + case _:tpd.Template => Template + case _:tpd.Block => Local + case _ => Other + + /** A container for the results of the used elements analysis */ + case class UnusedResult(warnings: List[(dotty.tools.dotc.util.SrcPos, WarnTypes)], usedImports: List[(tpd.Import, untpd.ImportSelector)]) +end CheckUnused + diff --git a/tests/neg-custom-args/fatal-warnings/i15503-scala2/scala2-t11681.scala b/tests/neg-custom-args/fatal-warnings/i15503-scala2/scala2-t11681.scala new file mode 100644 index 000000000000..f04129a19e48 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503-scala2/scala2-t11681.scala @@ -0,0 +1,110 @@ +// scalac: -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, // error + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // OK + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + b: String, // OK + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // OK + + def i(implicit s: String) = answer // error + + /* + 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) = ??? // OK + override def f(a: Int, b: String, c: Double): Int = a + b.toInt + c.toInt +} + +class Unusing(u: Int) { // error + def f = ??? +} + +class Valuing(val u: Int) // OK + +class Revaluing(u: Int) { def f = u } // OK + +case class CaseyKasem(k: Int) // OK + +case class CaseyAtTheBat(k: Int)(s: String) // error + +trait Ignorance { + def f(readResolve: Int) = answer // error +} + +class Reusing(u: Int) extends Unusing(u) // OK + +// TODO: check +// class Main { +// def main(args: Array[String]): Unit = println("hello, args") // OK +// } + +trait Unimplementation { + def f(u: Int): Int = ??? // OK +} + +trait DumbStuff { + def f(implicit dummy: DummyImplicit) = answer // todo // error + def g(dummy: DummyImplicit) = answer // error +} +trait Proofs { + def f[A, B](implicit ev: A =:= B) = answer // todo // error + def g[A, B](implicit ev: A <:< B) = answer // todo // error + def f2[A, B](ev: A =:= B) = answer // error + def g2[A, B](ev: A <:< B) = answer // error +} + +trait Anonymous { + def f = (i: Int) => answer // error + + def f1 = (_: Int) => answer // OK + + def f2: Int => Int = _ + 1 // OK + + def g = for (i <- List(1)) yield answer // error +} +trait Context[A] +trait Implicits { + def f[A](implicit ctx: Context[A]) = answer // error + def g[A: Context] = answer // error +} +class Bound[A: Context] // error +object Answers { + def answer: Int = 42 +} + +val a$1 = 2 \ No newline at end of file diff --git a/tests/neg-custom-args/fatal-warnings/i15503a.scala b/tests/neg-custom-args/fatal-warnings/i15503a.scala new file mode 100644 index 000000000000..28482c7addde --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503a.scala @@ -0,0 +1,253 @@ +// scalac: -Wunused:imports + + +object FooUnused: + import collection.mutable.Set // error + import collection.mutable.{Map => MutMap} // error + import collection.mutable._ // error + +object FooWildcardUnused: + import collection.mutable._ // error + +object Foo: + import collection.mutable.Set // OK + import collection.mutable.{Map => MutMap} // OK + + val bar = Set() // OK + val baz = MutMap() // OK + +object FooWildcard: + import collection.mutable._ // OK + + val bar = Set() // OK + +object FooNestedUnused: + import collection.mutable.Set // error + object Nested: + def hello = 1 + +object FooNested: + import collection.mutable.Set // OK + object Nested: + def hello = Set() + +object FooGivenUnused: + import SomeGivenImports.given // error + +object FooGiven: + import SomeGivenImports.given // OK + import SomeGivenImports._ // error + + val foo = summon[Int] + +/** + * Import used as type name are considered + * as used. + * + * Import here are only used as types, not as + * Term + */ +object FooTypeName: + import collection.mutable.Set // OK + import collection.mutable.Map // OK + import collection.mutable.Seq // OK + import collection.mutable.ArrayBuilder // OK + import collection.mutable.ListBuffer // error + + def checkImplicit[A](using Set[A]) = () + def checkParamType[B](a: Map[B,B]): Seq[B] = ??? + def checkTypeParam[A] = () + + checkTypeParam[ArrayBuilder[Int]] + + +object InlineChecks: + object InlineFoo: + import collection.mutable.Set // OK + import collection.mutable.Map // error + inline def getSet = Set(1) + + object InlinedBar: + import collection.mutable.Set // error + import collection.mutable.Map // error + val a = InlineFoo.getSet + +object MacroChecks: + object StringInterpol: + import collection.mutable.Set // OK + import collection.mutable.Map // OK + println(s"This is a mutableSet : ${Set[Map[Int,Int]]()}") + + +object InnerMostCheck: + import collection.mutable.* // error + def check = + import collection.mutable.* //OK + val a = Set(1) + +object IgnoreExclusion: + import collection.mutable.{Set => _} // OK + import collection.mutable.{Map => _} // OK + import collection.mutable.{ListBuffer} // error + def check = + val a = Set(1) + val b = Map(1 -> 2) +/** + * Some given values for the test + */ +object SomeGivenImports: + given Int = 0 + given String = "foo" + +/* BEGIN : Check on packages*/ +package testsamepackageimport: + package p { + class C + } + + package p { + import p._ // error + package q { + class U { + def f = new C + } + } + } +// ----------------------- + +package testpackageimport: + package a: + val x: Int = 0 + + package b: + import a._ // error + + +/* END : Check on packages*/ + +/* BEGIN : tests on meta-language features */ +object TestGivenCoversionScala2: + /* note: scala3 Conversion[U,T] do not require an import */ + import language.implicitConversions // OK + + implicit def doubleToInt(d:Double):Int = d.toInt + + def idInt(i:Int):Int = i + val someInt = idInt(1.0) + +object TestTailrecImport: + import annotation.tailrec // OK + @tailrec + def fac(x:Int, acc:Int = 1): Int = + if x == 0 then acc else fac(x - 1, acc * x) +/* END : tests on meta-language features */ + +/* BEGIN : tests on given import order */ +object GivenImportOrderAtoB: + class X + class Y extends X + object A { implicit val x: X = new X } + object B { implicit val y: Y = new Y } + class C { + import A._ // error + import B._ // OK + def t = implicitly[X] + } + +object GivenImportOrderBtoA: + class X + class Y extends X + object A { implicit val x: X = new X } + object B { implicit val y: Y = new Y } + class C { + import B._ // OK + import A._ // error + def t = implicitly[X] + } +/* END : tests on given import order */ + +/* Scala 2 implicits */ +object Scala2ImplicitsGiven: + object A: + implicit val x: Int = 1 + object B: + import A.given // OK + val b = summon[Int] + object C: + import A.given // error + val b = 1 + object D: + import A._ // OK + val b = summon[Int] + object E: + import A._ // error + val b = 1 + object F: + import A.x // OK + val b = summon[Int] + object G: + import A.x // error + val b = 1 + +// ------------------------------------- +object TestNewKeyword: + object Foo: + class Aa[T](val x: T) + object Bar: + import Foo.Aa // OK + val v = 1 + val a = new Aa(v) + +// ------------------------------------- +object testAnnotatedType: + import annotation.switch // OK + val a = (??? : @switch) match + case _ => ??? + + +//------------------------------------- +package testImportsInImports: + package a: + package b: + val x = 1 + package c: + import a.b // OK + import b.x // OK + val y = x + +//------------------------------------- +package testOnOverloadedMethodsImports: + package a: + trait A + trait B + trait C: + def foo(x: A):A = ??? + def foo(x: B):B = ??? + package b: + object D extends a.C + package c: + import b.D.foo // error + package d: + import b.D.foo // OK + def bar = foo((??? : a.A)) + package e: + import b.D.foo // OK + def bar = foo((??? : a.B)) + package f: + import b.D.foo // OK + def bar = foo((??? : a.A)) + def baz = foo((??? : a.B)) + +//------------------------------------- +package foo.testing.rename.imports: + import collection.mutable.{Set => MutSet1} // OK + import collection.mutable.{Set => MutSet2} // OK + import collection.mutable.{Set => MutSet3} // error + type A[X] = MutSet1[X] + val a = MutSet2(1) + +//------------------------------------- +package foo.testing.imports.precedence: + import scala.collection.immutable.{BitSet => _, _} // error + import scala.collection.immutable.BitSet // OK + def t = BitSet.empty \ No newline at end of file diff --git a/tests/neg-custom-args/fatal-warnings/i15503b.scala b/tests/neg-custom-args/fatal-warnings/i15503b.scala new file mode 100644 index 000000000000..0587bf781299 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503b.scala @@ -0,0 +1,89 @@ +// scalac: -Wunused:locals + +val a = 1 // OK + +val b = // OK + val e1 = 1 // error + def e2 = 2 // error + 1 + +val c = // OK + val e1 = 1 // OK + def e2 = e1 // OK + e2 + +def d = 1 // OK + +def e = // OK + val e1 = 1 // error + def e2 = 2 // error + 1 + +def f = // OK + val f1 = 1 // OK + def f2 = f1 // OK + f2 + +class Foo { + val b = // OK + val e1 = 1 // error + def e2 = 2 // error + 1 + + val c = // OK + val e1 = 1 // OK + def e2 = e1 // OK + e2 + + def d = 1 // OK + + def e = // OK + val e1 = 1 // error + def e2 = 2 // error + 1 + + def f = // OK + val f1 = 1 // OK + def f2 = f1 // OK + f2 +} + +// ---- SCALA 2 tests ---- + +package foo.scala2.tests: + class Outer { + class Inner + } + + trait Locals { + def f0 = { + var x = 1 // error + var y = 2 // OK + y = 3 + y + y + } + def f1 = { + val a = new Outer // OK + val b = new Outer // error + new a.Inner + } + def f2 = { + var x = 100 + x + } + } + + object Types { + def l1() = { + object HiObject { def f = this } // error + class Hi { // error + def f1: Hi = new Hi + def f2(x: Hi) = x + } + class DingDongDoobie // error + class Bippy // OK + type Something = Bippy // OK + type OtherThing = String // error + (new Bippy): Something + } + } diff --git a/tests/neg-custom-args/fatal-warnings/i15503c.scala b/tests/neg-custom-args/fatal-warnings/i15503c.scala new file mode 100644 index 000000000000..e34587fa5802 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503c.scala @@ -0,0 +1,20 @@ +// scalac: -Wunused:privates +trait C +class A: + self: C => // OK + class B: + private[A] val a = 1 // OK + private[B] val b = 1 // OK + private[this] val c = 1 // error + private val d = 1 // error + + private[A] val e = 1 // OK + private[this] val f = e // OK + private val g = f // OK + + private def fac(x: Int): Int = // error + if x == 0 then 1 else x * fac(x - 1) + + val x = 1 // OK + def y = 2 // OK + def z = g // OK \ No newline at end of file diff --git a/tests/neg-custom-args/fatal-warnings/i15503d.scala b/tests/neg-custom-args/fatal-warnings/i15503d.scala new file mode 100644 index 000000000000..6c5973c66a3a --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503d.scala @@ -0,0 +1,30 @@ +// scalac: -Wunused:unsafe-warn-patvars +// todo : change to :patvars + +sealed trait Calc +sealed trait Const extends Calc +case class Sum(a: Calc, b: Calc) extends Calc +case class S(pred: Const) extends Const +case object Z extends Const + +val a = Sum(S(S(Z)),Z) match { + case Sum(a,Z) => Z // error + // case Sum(a @ _,Z) => Z // todo : this should pass in the future + case Sum(a@S(_),Z) => Z // error + case Sum(a@S(_),Z) => a // OK + case Sum(a@S(b@S(_)), Z) => a // error + case Sum(a@S(b@S(_)), Z) => a // error + case Sum(a@S(b@(S(_))), Z) => Sum(a,b) // OK + case Sum(_,_) => Z // OK + case _ => Z // OK +} + +// todo : This should pass in the future +// val b = for { +// case Some(x) <- Option(Option(1)) +// } println(s"$x") + +// todo : This should *NOT* pass in the future +// val c = for { +// case Some(x) <- Option(Option(1)) +// } println(s"hello world") diff --git a/tests/neg-custom-args/fatal-warnings/i15503e.scala b/tests/neg-custom-args/fatal-warnings/i15503e.scala new file mode 100644 index 000000000000..79112942a205 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503e.scala @@ -0,0 +1,54 @@ +// scalac: -Wunused:explicits + +/* This goes around the "trivial method" detection */ +val default_val = 1 + +def f1(a: Int) = a // OK +def f2(a: Int) = default_val // error +def f3(a: Int)(using Int) = a // OK +def f4(a: Int)(using Int) = default_val // error +def f6(a: Int)(using Int) = summon[Int] // error +def f7(a: Int)(using Int) = summon[Int] + a // OK + +package scala2main.unused.args: + object happyBirthday { + def main(args: Array[String]): Unit = println("Hello World") // error + } + +package scala2main: + object happyBirthday { + def main(args: Array[String]): Unit = // OK + println(s"Hello World, there are ${args.size} arguments") + } + +package scala3main: + /* This goes around the "trivial method" detection */ + val default_unit = () + @main def hello = println("Hello World") // OK + +package foo.test.lambda.param: + val default_val = 1 + val a = (i: Int) => i // OK + val b = (i: Int) => default_val // error + val c = (_: Int) => default_val // OK + +package foo.test.trivial: + /* A twisted test from Scala 2 */ + class C { + def answer: 42 = 42 + object X + def g0(x: Int) = ??? // OK + def f0(x: Int) = () // OK + def f1(x: Int) = throw new RuntimeException // OK + def f2(x: Int) = 42 // OK + def f3(x: Int): Option[Int] = None // OK + def f4(x: Int) = classOf[Int] // OK + def f5(x: Int) = answer + 27 // OK + def f6(x: Int) = X // OK + def f7(x: Int) = Y // OK + def f8(x: Int): List[C] = Nil // OK + def f9(x: Int): List[Int] = List(1,2,3,4) // error + def foo:Int = 32 // OK + def f77(x: Int) = foo // error + } + object Y diff --git a/tests/neg-custom-args/fatal-warnings/i15503f.scala b/tests/neg-custom-args/fatal-warnings/i15503f.scala new file mode 100644 index 000000000000..db695da3490b --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503f.scala @@ -0,0 +1,12 @@ +// scalac: -Wunused:implicits + +/* This goes around the "trivial method" detection */ +val default_int = 1 + +def f1(a: Int) = a // OK +def f2(a: Int) = 1 // OK +def f3(a: Int)(using Int) = a // error +def f4(a: Int)(using Int) = default_int // error +def f6(a: Int)(using Int) = summon[Int] // OK +def f7(a: Int)(using Int) = summon[Int] + a // OK + diff --git a/tests/neg-custom-args/fatal-warnings/i15503g.scala b/tests/neg-custom-args/fatal-warnings/i15503g.scala new file mode 100644 index 000000000000..d4daea944184 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503g.scala @@ -0,0 +1,15 @@ +// scalac: -Wunused:params + +/* This goes around the "trivial method" detection */ +val default_int = 1 + +def f1(a: Int) = a // OK +def f2(a: Int) = default_int // error +def f3(a: Int)(using Int) = a // error +def f4(a: Int)(using Int) = default_int // error // error +def f6(a: Int)(using Int) = summon[Int] // error +def f7(a: Int)(using Int) = summon[Int] + a // OK + +/* --- Trivial method check --- */ +def g1(x: Int) = 1 // OK +def g2(x: Int) = ??? // OK \ No newline at end of file diff --git a/tests/neg-custom-args/fatal-warnings/i15503h.scala b/tests/neg-custom-args/fatal-warnings/i15503h.scala new file mode 100644 index 000000000000..f8d1d6f2202f --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503h.scala @@ -0,0 +1,20 @@ +// scalac: -Wunused:linted + +import collection.mutable.Set // error + +class A { + private val a = 1 // error + val b = 2 // OK + + private def c = 2 // error + def d(using x:Int): Int = b // error + def e(x: Int) = 1 // OK + def f = + val x = 1 // error + def f = 2 // error + 3 + + def g(x: Int): Int = x match + case x:1 => 0 // OK + case _ => 1 +} \ No newline at end of file diff --git a/tests/neg-custom-args/fatal-warnings/i15503i.scala b/tests/neg-custom-args/fatal-warnings/i15503i.scala new file mode 100644 index 000000000000..cecb79b70a34 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503i.scala @@ -0,0 +1,67 @@ +// scalac: -Wunused:all + +import collection.mutable.{Map => MutMap} // error +import collection.mutable.Set // error + +class A { + import collection.mutable.{Map => MutMap} // OK + private val a = 1 // error + val b = 2 // OK + + /* This goes around the trivial method detection */ + val default_int = 12 + + val someMap = MutMap() + + private def c1 = 2 // error + private def c2 = 2 // OK + def c3 = c2 + + def d1(using x:Int): Int = default_int // error + def d2(using x:Int): Int = x // OK + + def e1(x: Int) = default_int // error + def e2(x: Int) = x // OK + def f = + val x = 1 // error + def f = 2 // error + val y = 3 // OK + def g = 4 // OK + y + g + + // todo : uncomment once patvars is fixed + // def g(x: Int): Int = x match + // case x:1 => 0 // ?error + // case x:2 => x // ?OK + // case _ => 1 // ?OK +} + +/* ---- CHECK scala.annotation.unused ---- */ +package foo.test.scala.annotation: + import annotation.unused // OK + + /* This goes around the trivial method detection */ + val default_int = 12 + + def a1(a: Int) = a // OK + def a2(a: Int) = default_int // error + def a3(@unused a: Int) = default_int //OK + + def b1 = + def f = 1 // error + 1 + + def b2 = + def f = 1 // OK + f + + def b3 = + @unused def f = 1 // OK + 1 + + object Foo: + private def a = 1 // error + private def b = 2 // OK + @unused private def c = 3 // OK + + def other = b diff --git a/tests/neg-custom-args/fatal-warnings/i15503j.scala b/tests/neg-custom-args/fatal-warnings/i15503j.scala new file mode 100644 index 000000000000..51c1fa6fda0c --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15503j.scala @@ -0,0 +1,59 @@ +// scalac: -Wunused:strict-no-implicit-warn + +package foo.unused.strict.test: + package a: + given x: Int = 0 + implicit val y: Int = 1 + val z: Int = 2 + def f: Int = 3 + package b: + import a.given // OK + import a._ // OK + import a.* // OK + import a.x // OK + import a.y // OK + import a.z // error + import a.f // error + package c: + import a.given // OK + import a.x // OK + import a.y // OK + import a.z // OK + import a.f // OK + def g = f + z + y + x + +package foo.implicits.resolution: + class X + class Y extends X + object A { implicit val x: X = new X } + object B { implicit val y: Y = new Y } + class C { + import A._ // OK + import B._ // OK + def t = implicitly[X] + } + +package foo.unused.summon.inlines: + package lib: + trait A + trait B + trait C + trait X + + given willBeUnused: (A & X) = new A with X {} + given willBeUsed: (A & B) = new A with B {} + + package use: + import lib.{A, B, C, willBeUnused, willBeUsed} //OK + import compiletime.summonInline //OK + + transparent inline given conflictInside: C = + summonInline[A] + new {} + + transparent inline given potentialConflict: C = + summonInline[B] + new {} + + val b: B = summon[B] + val c: C = summon[C] \ No newline at end of file