diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index e632def24700..1e07254808d6 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -818,7 +818,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName - returnType = asmMethodType(dd.symbol).returnType + returnType = asmMethodType(methSymbol).returnType isMethSymStaticCtor = methSymbol.isStaticConstructor resetMethodBookkeeping(dd) @@ -915,7 +915,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) } } - if (isMethSymStaticCtor) { appendToStaticCtor(dd) } + if (isMethSymStaticCtor) { appendToStaticCtor() } } // end of emitNormalMethodBody() lineNumber(rhs) @@ -936,7 +936,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { * * TODO document, explain interplay with `fabricateStaticInitAndroid()` */ - private def appendToStaticCtor(dd: DefDef): Unit = { + private def appendToStaticCtor(): Unit = { def insertBefore( location: asm.tree.AbstractInsnNode, diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index d8ba1ab5dc2e..011f4b1c3a63 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(CheckUnused.PostTyper(), 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(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/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 67e1885b511f..f7de9b65d9e5 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -47,6 +47,14 @@ object desugar { */ val UntupledParam: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that a ValDef originated from a pattern. + */ + val PatternVar: Property.Key[Unit] = Property.StickyKey() + + /** An attachment key for Trees originating in for-comprehension, such as tupling of assignments. + */ + val ForArtifact: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that a ValDef is an evidence parameter * for a context bound. */ @@ -1505,7 +1513,7 @@ object desugar { val matchExpr = if (tupleOptimizable) rhs else - val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids)) + val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) vars match { case Nil if !mods.is(Lazy) => @@ -1535,6 +1543,7 @@ object desugar { ValDef(named.name.asTermName, tpt, selector(n)) .withMods(mods) .withSpan(named.span) + .withAttachment(PatternVar, ()) ) flatTree(firstDef :: restDefs) } @@ -1920,6 +1929,7 @@ object desugar { val vdef = ValDef(named.name.asTermName, tpt, rhs) .withMods(mods) .withSpan(original.span.withPoint(named.span.start)) + .withAttachment(PatternVar, ()) val mayNeedSetter = valDef(vdef) mayNeedSetter } @@ -2165,7 +2175,7 @@ object desugar { case _ => Modifiers() makePatDef(valeq, mods, defpat, rhs) } - val rhs1 = makeFor(nme.map, nme.flatMap, GenFrom(defpat0, gen.expr, gen.checkMode) :: Nil, Block(pdefs, makeTuple(id0 :: ids))) + val rhs1 = makeFor(nme.map, nme.flatMap, GenFrom(defpat0, gen.expr, gen.checkMode) :: Nil, Block(pdefs, makeTuple(id0 :: ids).withAttachment(ForArtifact, ()))) val allpats = gen.pat :: pats val vfrom1 = GenFrom(makeTuple(allpats), rhs1, GenCheckMode.Ignore) makeFor(mapName, flatMapName, vfrom1 :: rest1, body) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 599812a9a390..320622158386 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -173,28 +173,20 @@ private sealed trait WarningSettings: 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("imports", "Warn if an import selector is not referenced."), 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("patvars","Warn if a variable bound in a pattern is unused"), + ChoiceWithHelp("inlined", "Apply -Wunused to inlined expansions"), 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 ) @@ -206,7 +198,6 @@ private sealed trait WarningSettings: // Is any choice set for -Wunused? def any(using Context): Boolean = Wall.value || Wunused.value.nonEmpty - // overrided by strict-no-implicit-warn def imports(using Context) = (allOr("imports") || allOr("linted")) && !(strictNoImplicitWarn) def locals(using Context) = @@ -220,9 +211,8 @@ private sealed trait WarningSettings: 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 patvars(using Context) = allOr("patvars") + def inlined(using Context) = isChoiceSet("inlined") def linted(using Context) = allOr("linted") def strictNoImplicitWarn(using Context) = diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2890bdf306be..260f741ff6f6 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) @@ -1061,6 +1063,7 @@ class Definitions { @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") + @tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field") diff --git a/compiler/src/dotty/tools/dotc/report.scala b/compiler/src/dotty/tools/dotc/report.scala index 2ccf918e12fa..7f1eeb8e22eb 100644 --- a/compiler/src/dotty/tools/dotc/report.scala +++ b/compiler/src/dotty/tools/dotc/report.scala @@ -1,15 +1,12 @@ package dotty.tools.dotc -import reporting.* -import Diagnostic.* -import util.{SourcePosition, NoSourcePosition, SrcPos} -import core.* -import Contexts.*, Flags.*, Symbols.*, Decorators.* -import config.SourceVersion import ast.* -import config.Feature.sourceVersion +import core.*, Contexts.*, Flags.*, Symbols.*, Decorators.* +import config.Feature.sourceVersion, config.{MigrationVersion, SourceVersion} +import reporting.*, Diagnostic.* +import util.{SourcePosition, NoSourcePosition, SrcPos} + import java.lang.System.currentTimeMillis -import dotty.tools.dotc.config.MigrationVersion object report: @@ -55,6 +52,9 @@ object report: else issueWarning(new FeatureWarning(msg, pos.sourcePos)) end featureWarning + def warning(msg: Message, pos: SrcPos, origin: String)(using Context): Unit = + issueWarning(LintWarning(msg, addInlineds(pos), origin)) + def warning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new Warning(msg, addInlineds(pos))) diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index 6a2d88f4e82f..102572b82bbc 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -36,6 +36,18 @@ object Diagnostic: pos: SourcePosition ) extends Error(msg, pos) + /** A Warning with an origin. The semantics of `origin` depend on the warning. + * For example, an unused import warning has an origin that specifies the unused selector. + * The origin of a deprecation is the deprecated element. + */ + trait OriginWarning(val origin: String): + self: Warning => + + /** Lints are likely to be filtered. Provide extra axes for filtering by `-Wconf`. + */ + class LintWarning(msg: Message, pos: SourcePosition, origin: String) + extends Warning(msg, pos), OriginWarning(origin) + class Warning( msg: Message, pos: SourcePosition @@ -73,13 +85,9 @@ object Diagnostic: def enablingOption(using Context): Setting[Boolean] = ctx.settings.unchecked } - class DeprecationWarning( - msg: Message, - pos: SourcePosition, - val origin: String - ) extends ConditionalWarning(msg, pos) { + class DeprecationWarning(msg: Message, pos: SourcePosition, origin: String) + extends ConditionalWarning(msg, pos), OriginWarning(origin): def enablingOption(using Context): Setting[Boolean] = ctx.settings.deprecation - } class MigrationWarning( msg: Message, @@ -104,5 +112,5 @@ class Diagnostic( override def diagnosticRelatedInformation: JList[interfaces.DiagnosticRelatedInformation] = Collections.emptyList() - override def toString: String = s"$getClass at $pos: $message" + override def toString: String = s"$getClass at $pos L${pos.line+1}: $message" end Diagnostic diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 1896e5269d6c..22098ed71050 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -14,11 +14,13 @@ import scala.annotation.internal.sharable import scala.util.matching.Regex enum MessageFilter: - def matches(message: Diagnostic): Boolean = this match + def matches(message: Diagnostic): Boolean = + import Diagnostic.* + this match case Any => true - case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning] - case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] - case Unchecked => message.isInstanceOf[Diagnostic.UncheckedWarning] + case Deprecated => message.isInstanceOf[DeprecationWarning] + case Feature => message.isInstanceOf[FeatureWarning] + case Unchecked => message.isInstanceOf[UncheckedWarning] case MessageID(errorId) => message.msg.errorId == errorId case MessagePattern(pattern) => val noHighlight = message.msg.message.replaceAll("\\e\\[[\\d;]*[^\\d;]","") @@ -31,7 +33,7 @@ enum MessageFilter: pattern.findFirstIn(path).nonEmpty case Origin(pattern) => message match - case message: Diagnostic.DeprecationWarning => pattern.findFirstIn(message.origin).nonEmpty + case message: OriginWarning => pattern.findFirstIn(message.origin).nonEmpty case _ => false case None => false @@ -56,12 +58,12 @@ object WConf: private type Conf = (List[MessageFilter], Action) def parseAction(s: String): Either[List[String], Action] = s match - case "error" | "e" => Right(Error) - case "warning" | "w" => Right(Warning) - case "verbose" | "v" => Right(Verbose) - case "info" | "i" => Right(Info) - case "silent" | "s" => Right(Silent) - case _ => Left(List(s"unknown action: `$s`")) + case "error" | "e" => Right(Error) + case "warning" | "w" => Right(Warning) + case "verbose" | "v" => Right(Verbose) + case "info" | "i" => Right(Info) + case "silent" | "s" => Right(Silent) + case _ => Left(List(s"unknown action: `$s`")) private def regex(s: String) = try Right(s.r) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index ab4f40677371..d9d5c214c958 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3290,22 +3290,24 @@ extends TypeMsg(ConstructorProxyNotValueID): |companion value with the (term-)name `A`. However, these context bound companions |are not values themselves, they can only be referred to in selections.""" -class UnusedSymbol(errorText: String)(using Context) +class UnusedSymbol(errorText: String, val actions: List[CodeAction] = Nil)(using Context) extends Message(UnusedSymbolID) { def kind = MessageKind.UnusedSymbol override def msg(using Context) = errorText 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") -} + override def actions(using Context) = this.actions +} + +object UnusedSymbol: + def imports(actions: List[CodeAction])(using Context): UnusedSymbol = UnusedSymbol(i"unused import", actions) + 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") + def unsetLocals(using Context): UnusedSymbol = UnusedSymbol(i"unset local variable, consider using an immutable val instead") + def unsetPrivates(using Context): UnusedSymbol = UnusedSymbol(i"unset private variable, consider using an immutable val instead") 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..3247a0123afb 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 @@ -91,16 +87,16 @@ class CheckShadowing extends MiniPhase: ) override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = + val sym = tree.symbol 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)) - else - ctx - + if isValidTypeParamOwner(tree.symbol.owner) then + shadowingDataApply(sd => sd.registerCandidate(parent, tree)) + ctx override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = shadowingDataApply(sd => sd.outOfScope()) @@ -115,13 +111,14 @@ 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 + // Do not register for constructors the work is done for the Class owned equivalent TypeDef + if tree.symbol.is(Param) && isValidTypeParamOwner(tree.symbol.owner) then 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 + // 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) @@ -141,7 +138,7 @@ class CheckShadowing extends MiniPhase: override def traverse(tree: tpd.Tree)(using Context): Unit = tree match - case t:tpd.TypeDef => + case t: tpd.TypeDef => val newCtx = shadowingDataApply(sd => sd.registerCandidate(parent, t) ) @@ -157,7 +154,7 @@ class CheckShadowing extends MiniPhase: override def traverse(tree: tpd.Tree)(using Context): Unit = tree match - case t:tpd.Import => + case t: tpd.Import => val newCtx = shadowingDataApply(sd => sd.registerImport(t)) traverseChildren(tree)(using newCtx) case _ => @@ -240,7 +237,7 @@ object CheckShadowing: val declarationScope = ctx.effectiveScope val res = declarationScope.lookup(symbol.name) res match - case s: Symbol if s.isType => Some(s) + case s: Symbol if s.isType && s != symbol => Some(s) case _ => lookForUnitShadowedType(symbol)(using ctx.outer) /** Register if the valDef is a private declaration that shadows an inherited field */ @@ -310,4 +307,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 6e626fc5dd9e..976ca191b185 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,883 +1,865 @@ package dotty.tools.dotc.transform -import scala.annotation.tailrec -import scala.collection.mutable - -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.desugar.{ForArtifact, PatternVar} +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.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.core.Names.{Name, SimpleName, DerivedName, TermName, termName} +import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName} +import dotty.tools.dotc.core.NameKinds.{ContextBoundParamName, ContextFunctionParamName, WildcardParamName} +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} +import dotty.tools.dotc.core.Types.* 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.Flags -import dotty.tools.dotc.core.Names.{Name, TermName} -import dotty.tools.dotc.core.NameOps.isReplWrapperName +import dotty.tools.dotc.reporting.{CodeAction, UnusedSymbol} +import dotty.tools.dotc.rewrites.Rewrites 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.util.Spans.Span -import scala.math.Ordering +import dotty.tools.dotc.typer.{ImportInfo, Typer} +import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span +import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace} +import dotty.tools.dotc.util.chaining.* +import java.util.IdentityHashMap -/** - * 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 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 = - ctx.property(_key) match - case Some(ud) => f(ud) - case None => () - ctx +import scala.collection.mutable, mutable.{ArrayBuilder, ListBuffer, Stack} - override def phaseName: String = CheckUnused.phaseNamePrefix + suffix +import CheckUnused.* - override def description: String = CheckUnused.description +/** A compiler phase that checks for unused imports or definitions. + */ +class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPhase: - override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.WunusedHas.any && - !ctx.isJava + override def phaseName: String = s"checkUnused$suffix" - // ========== SETUP ============ + override def description: String = "check for unused elements" - override def prepareForUnit(tree: tpd.Tree)(using Context): Context = - val data = UnusedData() - tree.getAttachment(_key).foreach(oldData => - data.unusedAggregate = oldData.unusedAggregate - ) - val fresh = ctx.fresh.setProperty(_key, data) - tree.putAttachment(_key, data) - fresh - - // ========== END + REPORTING ========== - - override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = - unusedDataApply { ud => - ud.finishAggregation() - if(phaseMode == PhaseMode.Report) then - ud.unusedAggregate.foreach(reportUnused) - } - tree + override def isEnabled(using Context): Boolean = ctx.settings.WunusedHas.any - // ========== 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 isRunnable(using Context): Boolean = super.isRunnable && ctx.settings.WunusedHas.any && !ctx.isJava - override def prepareForInlined(tree: tpd.Inlined)(using Context): Context = - traverser.traverse(tree.call) - ctx + override def prepareForUnit(tree: Tree)(using Context): Context = + val infos = tree.getAttachment(refInfosKey).getOrElse: + RefInfos().tap(tree.withAttachment(refInfosKey, _)) + ctx.fresh.setProperty(refInfosKey, infos) + override def transformUnit(tree: Tree)(using Context): tree.type = + if phaseMode == PhaseMode.Report then + reportUnused() + tree.removeAttachment(refInfosKey) + tree - override def prepareForIdent(tree: tpd.Ident)(using Context): Context = + override def transformIdent(tree: Ident)(using Context): tree.type = if tree.symbol.exists then - unusedDataApply { ud => - @tailrec - 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) - loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1) - - loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) - ud.registerUsed(tree.symbol, Some(tree.name)) - } + // resolve if inlined at the position of the call, or is zero extent summon + val resolving = + refInfos.inlined.isEmpty + || tree.srcPos.isZeroExtentSynthetic + || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos)) + if resolving && !ignoreTree(tree) then + resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject) else if tree.hasType then - unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some(tree.name))) + resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) + tree + + // import x.y; x may be rewritten x.y, also import x.z as y + override def transformSelect(tree: Select)(using Context): tree.type = + val name = tree.removeAttachment(OriginalName).getOrElse(nme.NO_NAME) + if tree.span.isSynthetic && tree.symbol == defn.TypeTest_unapply then + tree.qualifier.tpe.underlying.finalResultType match + case AppliedType(_, args) => // if tycon.typeSymbol == defn.TypeTestClass + val res = args(1) + val target = res.dealias.typeSymbol + resolveUsage(target, target.name, res.importPrefix.skipPackageObject) // case _: T => + case _ => + else if tree.qualifier.span.isSynthetic || name.exists(_ != tree.symbol.name) then + if !ignoreTree(tree) then + resolveUsage(tree.symbol, name, tree.qualifier.tpe) else - ctx - - 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 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 = - 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 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) - traverseAnnotations(tree.symbol) - ud.registerDef(tree) - ud.addIgnoredUsage(tree.symbol) - - override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = - unusedDataApply: ud => - traverseAnnotations(tree.symbol) - 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 = - traverseAnnotations(tree.symbol) - unusedDataApply(_.registerPatVar(tree)) + refUsage(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 transformLiteral(tree: Literal)(using Context): tree.type = + tree.getAttachment(Typer.AdaptedTree).foreach(transformAllDeep) + tree - 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 prepareForCaseDef(tree: CaseDef)(using Context): Context = + nowarner.traverse(tree.pat) + ctx - // ========== MiniPhase Transform ========== + override def prepareForApply(tree: Apply)(using Context): Context = + // ignore tupling of for assignments, as they are not usages of vars + if tree.hasAttachment(ForArtifact) then + tree match + case Apply(TypeApply(Select(fun, nme.apply), _), args) => + if fun.symbol.is(Module) && defn.isTupleClass(fun.symbol.companionClass) then + args.foreach(_.withAttachment(ForArtifact, ())) + case _ => + ctx - override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def prepareForAssign(tree: Assign)(using Context): Context = + tree.lhs.putAttachment(Ignore, ()) // don't take LHS reference as a read + ctx + override def transformAssign(tree: Assign)(using Context): tree.type = + tree.lhs.removeAttachment(Ignore) + val sym = tree.lhs.symbol + if sym.exists then + refInfos.asss.addOne(sym) tree - override def transformTemplate(tree: tpd.Template)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def prepareForMatch(tree: Match)(using Context): Context = + // exonerate case.pat against tree.selector (simple var pat only for now) + tree.selector match + case Ident(nm) => tree.cases.foreach(k => absolveVariableBindings(List(nm), List(k.pat))) + case _ => + ctx + override def transformMatch(tree: Match)(using Context): tree.type = + if tree.isInstanceOf[InlineMatch] && tree.selector.isEmpty then + val sf = defn.Compiletime_summonFrom + resolveUsage(sf, sf.name, NoPrefix) tree - override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + 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 => resolveUsage(tpt.typeSymbol, tpt.typeSymbol.name, NoPrefix) + case _ => tree - override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForInlined(tree: Inlined)(using Context): Context = + refInfos.inlined.push(tree.call.srcPos) + ctx + override def transformInlined(tree: Inlined)(using Context): tree.type = + val _ = refInfos.inlined.pop() + if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then + transformAllDeep(tree.call) tree - override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) - tree + override def prepareForBind(tree: Bind)(using Context): Context = + refInfos.register(tree) + ctx - override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForValDef(tree: ValDef)(using Context): Context = + if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then + refInfos.register(tree) + ctx + override def transformValDef(tree: ValDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if tree.name.startsWith("derived$") && tree.hasType then + def loop(t: Tree): Unit = t match + case Ident(name) => + val target = + val ts0 = t.tpe.typeSymbol + if ts0.is(ModuleClass) then ts0.companionModule else ts0 + resolveUsage(target, name, t.tpe.underlyingPrefix.skipPackageObject) + case Select(t, _) => loop(t) + case _ => + tree.getAttachment(OriginalTypeClass).foreach(loop) tree + override def prepareForDefDef(tree: DefDef)(using Context): Context = + def trivial = tree.symbol.is(Deferred) || isUnconsuming(tree.rhs) + def nontrivial = tree.symbol.isConstructor || tree.symbol.isAnonymousFunction + if !nontrivial && trivial then refInfos.skip.addOne(tree.symbol) + if tree.symbol.is(Inline) then + refInfos.inliners += 1 + else if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then + refInfos.register(tree) + ctx + override def transformDefDef(tree: DefDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if tree.symbol.is(Inline) then + refInfos.inliners -= 1 + tree - // ---------- MiniPhase HELPERS ----------- + override def transformTypeDef(tree: TypeDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if !tree.symbol.is(Param) then // type parameter to do? + refInfos.register(tree) + tree - private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = - unusedDataApply { ud => - ud.pushScope(UnusedData.ScopeType.fromTree(tree)) - } - ctx + override def prepareForTemplate(tree: Template)(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def prepareForPackageDef(tree: PackageDef)(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def prepareForStats(trees: List[Tree])(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def transformOther(tree: Tree)(using Context): tree.type = + tree match + case imp: Import => + if phaseMode eq PhaseMode.Aggregate then + refInfos.register(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) => + // selftype of object is not a usage + val moduleSelfRef = ctx.owner.is(Module) && ctx.owner == tree.symbol.companionModule.moduleClass + if !moduleSelfRef then + 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 MatchTypeTree(bound, selector, cases) => + transformAllDeep(bound) + transformAllDeep(selector) + cases.foreach(transformAllDeep) + case ByNameTypeTree(result) => + transformAllDeep(result) + case _: InferredTypeTree => // do nothing + case _: Export => // nothing to do + case _ if tree.isType => + println(s"OTHER TYPE ${tree.getClass} ${tree.show}") + case _ => + println(s"OTHER ${tree.getClass} ${tree.show}") + tree - private def popOutBlockTemplatePackageDef()(using Context): Context = - unusedDataApply { ud => - ud.popScope() - } - ctx + private def traverseAnnotations(sym: Symbol)(using Context): Unit = + for annot <- sym.denot.annotations do + transformAllDeep(annot.tree) - /** - * This traverse is the **main** component of this phase - * - * It traverses the tree and gathers the data in the - * corresponding context property + /** Look up a reference in enclosing contexts to determine whether it was introduced by a definition or import. + * The binding of highest precedence must be correct. */ - 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) + def resolveUsage(sym: Symbol, name: Name, prefix: Type)(using Context): Unit = + def matchingSelector(info: ImportInfo): ImportSelector | Null = + val qtpe = info.site //info.qualifier.tpe.nn + def loop(sels: List[ImportSelector]): ImportSelector | Null = + sels match + case sel :: sels if sel.isWildcard => + // the qualifier must have the target symbol as a member + val matches = qtpe.member(sym.name).hasAltWith(_.symbol == sym) + && { + if sel.isGiven then // Further check that the symbol is a given or implicit and conforms to the bound + sym.isOneOf(Given | Implicit) + && (sel.bound.isEmpty || sym.info.finalResultType <:< sel.boundTpe) + && (prefix.eq(NoPrefix) || qtpe =:= prefix) + else + !sym.is(Given) // Normal wildcard, check that the symbol is not a given (but can be implicit) + } + if matches then sel else loop(sels) + case sel :: sels => + def hasAltMember(nm: Name) = qtpe.member(nm).hasAltWith(_.symbol == sym) + // if there is an explicit name, it must match + val matches = !name.exists(_.toTermName != sel.rename) && + (prefix.eq(NoPrefix) || qtpe =:= prefix) && (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName)) + if matches then sel else loop(sels) + case nil => null + loop(info.selectors) + + def checkMember(ctxsym: Symbol): Boolean = + ctxsym.isClass && sym.owner.isClass + && ctxsym.thisType.baseClasses.contains(sym.owner) + && ctxsym.thisType.member(sym.name).hasAltWith(d => d.containsSym(sym) && !name.exists(_ != d.name)) + + def addCached(where: Context): Unit = + if where.moreProperties ne null then + where.property(resolvedKey) match + case Some(res) => + val np = (name, prefix) + res.seen.updateWith(sym): + case svs @ Some(vs) => if vs.exists((n, p) => n == name && p =:= prefix) then svs else Some(np :: vs) + case _ => Some(np :: Nil) case _ => - traverseChildren(tp) - - /** This traverse the annotations of the symbol */ - private def traverseAnnotations(sym: Symbol)(using Context): Unit = - sym.denot.annotations.foreach(annot => traverser.traverse(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) - } + if !sym.exists then return + + // Names are resolved by definitions and imports, which have four precedence levels: + // 1. def from this compilation unit + // 2. specific import + // 3. wildcard import + // 4. def from another compilation unit via enclosing package + // We find the innermost, highest precedence. We have no nesting levels but assume correctness. + // If the sym is an enclosing definition (the owner of a context), it does not count toward usages. + val isLocal = sym.isLocalToBlock + var foundEnclosing = false + var candidate: Context = NoContext + var cachePoint: Context = NoContext // last context with Resolved cache + var importer: ImportSelector | Null = null // non-null for import context + var precedence = Int.MaxValue // of current resolution; lower is higher + var done = false + var cached = false + val ctxs = ctx.outersIterator + while !done && ctxs.hasNext do + val cur = ctxs.next() + if cur.owner eq sym then + foundEnclosing = true + done = true + else if isLocal then + if cur.owner eq sym.owner then done = true // only checking for enclosing + else + cached = + cur.property(resolvedKey) match + case Some(res) => + if precedence > 4 then cachePoint = cur // conservative, cache must be nested below the result context + res.saw(sym, name, prefix) + case _ => false + if cached then + candidate = cur + done = true + else if cur.isImportContext then + val sel = matchingSelector(cur.importInfo.nn) + if sel != null then + if cur.importInfo.nn.isRootImport then + if precedence > 4 then + precedence = 4 + candidate = cur + importer = sel + done = true + else if sel.isWildcard then + if precedence > 3 then + precedence = 3 + candidate = cur + importer = sel + else + if precedence > 2 then + precedence = 2 + candidate = cur + importer = sel + else if checkMember(cur.owner) then + if sym.srcPos.sourcePos.source == ctx.source then + precedence = 1 + candidate = cur + importer = null + done = true + else if precedence > 4 then + precedence = 4 + candidate = cur + end while + if !foundEnclosing then + refInfos.refs.addOne(sym) + if candidate != NoContext && candidate.isImportContext && importer != null + then refInfos.sels.put(importer, ()) + if !cached then + addCached(cachePoint) + if cachePoint ne ctx then + addCached(ctx) + end resolveUsage + // if sym is not an enclosing element, record the reference + def refUsage(sym: Symbol)(using Context): Unit = + if !ctx.outersIterator.exists(cur => cur.owner eq sym) then + refInfos.refs.addOne(sym) end CheckUnused object CheckUnused: - val phaseNamePrefix: String = "checkUnused" - val description: String = "check for unused elements" enum PhaseMode: case Aggregate case Report - private enum WarnTypes: - case Imports - case LocalDefs - case ExplicitParams - case ImplicitParams - case PrivateMembers - case PatVars - case UnsetLocals - case UnsetPrivates - - /** - * The key used to retrieve the "unused entity" analysis metadata, - * from the compilation `Context` - */ - private val _key = Property.StickyKey[UnusedData] + val refInfosKey = Property.StickyKey[RefInfos] + + val resolvedKey = Property.Key[Resolved] + + inline def refInfos(using Context): RefInfos = ctx.property(refInfosKey).get + + inline def resolved(using Context): Resolved = + ctx.property(resolvedKey) match + case Some(res) => res + case _ => throw new MatchError("no Resolved for context") + /** Attachment holding the name of an Ident as written by the user. */ val OriginalName = Property.StickyKey[Name] - class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key) + /** Attachment holding the name of a type class as written by the user. */ + val OriginalTypeClass = Property.StickyKey[Tree] - class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key) + /** Suppress warning in a tree, such as a patvar absolved of unused warning by special naming convention. */ + val NoWarn = Property.StickyKey[Unit] - /** - * A stateful class gathering the infos on : - * - imports - * - definitions - * - usage - */ - private class UnusedData: - import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer => MutList} - import UnusedData.* - - /** The current scope during the tree traversal */ - val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other) - - 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]] - /* unused import collected during traversal */ - private val unusedImport = MutList.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] - - /** All variables sets*/ - private val setVars = MutSet[Symbol]() - - /** 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]() - - private val paramsToSkip = MutSet[Symbol]() - - - def finishAggregation(using Context)(): Unit = - val unusedInThisStage = this.getUnused - this.unusedAggregate match { - case None => - this.unusedAggregate = Some(unusedInThisStage) - case Some(prevUnused) => - val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings) - this.unusedAggregate = Some(UnusedResult(intersection)) - } - - - /** - * 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], includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit = - if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) then - if sym.isConstructor then - registerUsed(sym.owner, None, includeForImport) // constructor are "implicitly" imported with the class - 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) - - def addIfExists(sym: Symbol): Unit = - if sym.exists then - usedDef += sym - if includeForImport1 then - usedInScope.top += ((sym, name, isDerived)) - addIfExists(sym) - addIfExists(sym.companionModule) - addIfExists(sym.companionClass) - if sym.sourcePos.exists then - for n <- name do - usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym - - /** Register a symbol that should be ignored */ - def addIgnoredUsage(sym: Symbol)(using Context): Unit = - doNotRegister ++= sym.everySymbol - - /** Remove a symbol that shouldn't be ignored anymore */ - def removeIgnoredUsage(sym: Symbol)(using Context): Unit = - doNotRegister --= sym.everySymbol - - def addIgnoredParam(sym: Symbol)(using Context): Unit = - paramsToSkip += sym - - /** Register an import */ - def registerImport(imp: tpd.Import)(using Context): Unit = - if - !tpd.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 - then - val qualTpe = imp.expr.tpe - - // Put wildcard imports at the end, because they have lower priority within one Import - val reorderdSelectors = - val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard) - nonWildcardSels ::: wildcardSels - - val excludedMembers: mutable.Set[TermName] = mutable.Set.empty - - val newDataInScope = - for sel <- reorderdSelectors 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 isImportExclusion(sel) then - excludedMembers += sel.name - if sel.isWildcard && excludedMembers.nonEmpty then - // mark excluded members for the wildcard import - data.markExcluded(excludedMembers.toSet) - data - impInScope.top.appendAll(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 = - if memDef.isValidMemberDef && !isDefIgnored(memDef) then - if memDef.isValidParam then - if memDef.symbol.isOneOf(GivenOrImplicit) then - if !paramsToSkip.contains(memDef.symbol) then - implicitParamInScope += memDef - else if !paramsToSkip.contains(memDef.symbol) then - explicitParamInScope += memDef - else if currScopeType.top == 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 - patVarsInScope += patvar - - /** enter a new scope */ - def pushScope(newScopeType: ScopeType): Unit = - // unused imports : - currScopeType.push(newScopeType) - impInScope.push(MutList()) - usedInScope.push(MutSet()) - - def registerSetVar(sym: Symbol): Unit = - setVars += sym - - /** - * leave the current scope and do : - * - * - If there are imports in this scope check for unused ones - */ - def popScope()(using Context): Unit = - currScopeType.pop() - val usedInfos = usedInScope.pop() - 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() - case None => - // Propagate the symbol one level up - if usedInScope.nonEmpty then - usedInScope.top += usedInfo - end for // each in `used` - - for selData <- selDatas do - if !selData.isUsed then - unusedImport += selData - 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 - */ + /** Ignore reference. */ + val Ignore = Property.StickyKey[Unit] - def getUnused(using Context): UnusedResult = - popScope() + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper") - def isUsedInPosition(name: Name, span: Span): Boolean = - usedInPosition.get(name) match - case Some(syms) => syms.exists(sym => span.contains(sym.span)) - case None => false + class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") - 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 - // 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 - // 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)) + class RefInfos: + val defs = mutable.Set.empty[(Symbol, SrcPos)] // definitions + val pats = mutable.Set.empty[(Symbol, SrcPos)] // pattern variables + val refs = mutable.Set.empty[Symbol] // references + val asss = mutable.Set.empty[Symbol] // targets of assignment + val skip = mutable.Set.empty[Symbol] // methods to skip (don't warn about their params) + val imps = new IdentityHashMap[Import, Unit] // imports + val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors + def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then + tree match + case imp: Import => + if inliners == 0 + && languageImport(imp.expr).isEmpty + && !imp.isGeneratedByEnum + && !ctx.outer.owner.name.isReplWrapperName + then + imps.put(imp, ()) + case tree: Bind => + if !tree.name.isInstanceOf[DerivedName] && !tree.name.is(WildcardParamName) && !tree.hasAttachment(NoWarn) then + pats.addOne((tree.symbol, tree.namePos)) + case tree: ValDef if tree.hasAttachment(PatternVar) => + if !tree.name.isInstanceOf[DerivedName] then + pats.addOne((tree.symbol, tree.namePos)) + case tree: NamedDefTree => + if (tree.symbol ne NoSymbol) && !tree.name.isWildcard then + defs.addOne((tree.symbol, tree.namePos)) + case _ => + if tree.symbol ne NoSymbol then + defs.addOne((tree.symbol, tree.srcPos)) + + val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code + var inliners = 0 // depth of inline def + end RefInfos + + // Symbols already resolved in the given Context (with name and prefix of lookup) + class Resolved: + val seen = mutable.Map.empty[Symbol, List[(Name, Type)]].withDefaultValue(Nil) + def saw(sym: Symbol, name: Name, pre: Type)(using Context): Boolean = + seen(sym).exists((n, p) => n == name && p =:= pre) + + def reportUnused()(using Context): Unit = + for (msg, pos, origin) <- warnings do + if origin.isEmpty then report.warning(msg, pos) + else report.warning(msg, pos, origin) + msg.actions.headOption.foreach(Rewrites.applyAction) + + type MessageInfo = (UnusedSymbol, SrcPos, String) // string is origin or empty + + def warnings(using Context): Array[MessageInfo] = + val actionable = ctx.settings.rewrite.value.nonEmpty + val warnings = ArrayBuilder.make[MessageInfo] + def warnAt(pos: SrcPos)(msg: UnusedSymbol, origin: String = ""): Unit = warnings.addOne((msg, pos, origin)) + val infos = refInfos + for (sym, pos) <- infos.defs.iterator if !sym.hasAnnotation(defn.UnusedAnnot) do + if infos.refs(sym) then + if sym.isLocalToBlock then + if ctx.settings.WunusedHas.locals && sym.is(Mutable) && !infos.asss(sym) then + warnAt(pos)(UnusedSymbol.unsetLocals) + else if ctx.settings.WunusedHas.privates && sym.isAllOf(Private | Mutable) && !infos.asss(sym) then + warnAt(pos)(UnusedSymbol.unsetPrivates) + else if sym.is(Private, butNot = ParamAccessor) then + if ctx.settings.WunusedHas.privates + && !sym.isConstructor + && sym.is(Private, butNot = SelfName | Synthetic | CaseAccessor) + && !sym.isSerializationSupport + && !(sym.is(Mutable) && sym.isSetter && sym.owner.is(Trait)) // tracks sym.underlyingSymbol sibling getter + then + warnAt(pos)(UnusedSymbol.privateMembers) + else if sym.is(Param, butNot = Given | Implicit) then + val m = sym.owner + def forgiven = + val dd = defn + m.isDeprecated + || m.is(Synthetic) && !m.isAnonymousFunction + || m.hasAnnotation(defn.UnusedAnnot) // param of unused method + || sym.info.isSingleton + || m.isConstructor && m.owner.thisType.baseClasses.contains(defn.AnnotationClass) + def checkExplicit(): Unit = + // A class param is unused if its param accessor is unused. + // (The class param is not assigned to a field until constructors.) + // A local param accessor warns as a param; a private accessor as a private member. + // Avoid warning for case class elements because they are aliased via unapply. + if m.isPrimaryConstructor then + val alias = m.owner.info.member(sym.name) + if alias.exists then + val aliasSym = alias.symbol + if aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor) && !infos.refs(alias.symbol) then + if aliasSym.is(Local) then + if ctx.settings.WunusedHas.explicits then + warnAt(pos)(UnusedSymbol.explicitParams) + else + if ctx.settings.WunusedHas.privates then + warnAt(pos)(UnusedSymbol.privateMembers) + else if ctx.settings.WunusedHas.explicits + && !sym.is(Synthetic) // param to setter is unused bc there is no field yet + && !(sym.owner.is(ExtensionMethod) && { + m.paramSymss.dropWhile(_.exists(_.isTypeParam)) match + case (h :: Nil) :: Nil => h == sym // param is the extended receiver + case _ => false + }) + && !sym.name.isInstanceOf[DerivedName] + && !ctx.platform.isMainMethod(m) + then + warnAt(pos)(UnusedSymbol.explicitParams) + end checkExplicit + if !infos.skip(m) + && !forgiven + then + checkExplicit() + else if sym.is(Param) then // Given | Implicit + val m = sym.owner + def forgiven = + val dd = defn + m.isDeprecated + || m.is(Synthetic) + || sym.name.is(ContextFunctionParamName) // a ubiquitous parameter + || sym.name.is(ContextBoundParamName) && sym.info.typeSymbol.isMarkerTrait // a ubiquitous parameter + || m.hasAnnotation(dd.UnusedAnnot) // param of unused method + || sym.info.typeSymbol.match // more ubiquity + case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true + case _ => false + || sym.info.isSingleton // DSL friendly + || sym.isCanEqual + || sym.info.typeSymbol.hasAnnotation(dd.LanguageFeatureMetaAnnot) + || sym.info.isInstanceOf[RefinedType] // can't be expressed as a context bound + if ctx.settings.WunusedHas.implicits + && !infos.skip(m) + && !forgiven + then + if m.isPrimaryConstructor then + val alias = m.owner.info.member(sym.name) + if alias.exists then + val aliasSym = alias.symbol + if aliasSym.is(ParamAccessor) && !infos.refs(alias.symbol) then + warnAt(pos)(UnusedSymbol.implicitParams) + else + warnAt(pos)(UnusedSymbol.implicitParams) + else if sym.isLocalToBlock then + if ctx.settings.WunusedHas.locals + && !sym.is(InlineProxy) + && !sym.isCanEqual + then + warnAt(pos)(UnusedSymbol.localDefs) + + if ctx.settings.WunusedHas.patvars then + // convert the one non-synthetic span so all are comparable + def uniformPos(sym: Symbol, pos: SrcPos): SrcPos = + if pos.span.isSynthetic then pos else pos.sourcePos.withSpan(pos.span.toSynthetic) + // patvars in for comprehensions share the pos of where the name was introduced + val byPos = infos.pats.groupMap(uniformPos(_, _))((sym, pos) => sym) + for (pos, syms) <- byPos if !syms.exists(_.hasAnnotation(defn.UnusedAnnot)) do + if !syms.exists(infos.refs(_)) then + if !syms.exists(v => !v.isLocal && !v.is(Private)) then + warnAt(pos)(UnusedSymbol.patVars) + else if syms.exists(_.is(Mutable)) then // check unassigned var + val sym = // recover the original + if syms.size == 1 then syms.head + else infos.pats.find((s, p) => syms.contains(s) && !p.span.isSynthetic).map(_._1).getOrElse(syms.head) + if sym.is(Mutable) && !infos.asss(sym) then + if sym.isLocalToBlock then + warnAt(pos)(UnusedSymbol.unsetLocals) + else if sym.is(Private) then + warnAt(pos)(UnusedSymbol.unsetPrivates) + + // TODO check for unused masking import + import scala.jdk.CollectionConverters.given + import Rewrites.ActionPatch + if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then + type ImpSel = (Import, ImportSelector) + def isUsable(imp: Import, sel: ImportSelector): Boolean = + sel.isImportExclusion || infos.sels.containsKey(sel) || imp.isLoose(sel) + def warnImport(warnable: ImpSel, actions: List[CodeAction] = Nil): Unit = + val (imp, sel) = warnable + val msg = UnusedSymbol.imports(actions) + // example collection.mutable.{Map as MutMap} + val origin = cpy.Import(imp)(imp.expr, List(sel)).show(using ctx.withoutColors).stripPrefix("import ") + warnAt(sel.srcPos)(msg, origin) + + if !actionable then + for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if !isUsable(imp, sel) do + warnImport(imp -> sel) + else + // If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.) + // If for deletion, and the prefix of the line is also blank, then include that, too. (Del blank line.) + def editPosAt(srcPos: SrcPos, forDeletion: Boolean): SrcPos = + val start = srcPos.span.start + val end = srcPos.span.end + val content = srcPos.sourcePos.source.content() + val prev = content.lastIndexWhere(c => !isWhitespace(c), end = start - 1) + val emptyLeft = prev < 0 || isLineBreakChar(content(prev)) + val next = content.indexWhere(c => !isWhitespace(c), from = end) + val emptyRight = next < 0 || isLineBreakChar(content(next)) + val deleteLine = emptyLeft && emptyRight && forDeletion + val bump = if (deleteLine) 1 else 0 // todo improve to include offset of next line, endline + 1 + val p0 = srcPos.span + val p1 = if (next >= 0 && emptyRight) p0.withEnd(next + bump) else p0 + val p2 = if (deleteLine) p1.withStart(prev + 1) else p1 + srcPos.sourcePos.withSpan(p2) + def actionsOf(actions: (SrcPos, String)*): List[CodeAction] = + val patches = actions.map((srcPos, replacement) => ActionPatch(srcPos.sourcePos, replacement)).toList + List(CodeAction(title = "unused import", description = Some("remove import"), patches)) + def replace(editPos: SrcPos)(replacement: String): List[CodeAction] = actionsOf(editPos -> replacement) + def deletion(editPos: SrcPos): List[CodeAction] = actionsOf(editPos -> "") + def textFor(impsel: ImpSel): String = + val (imp, sel) = impsel + val content = imp.srcPos.sourcePos.source.content() + def textAt(pos: SrcPos) = String(content.slice(pos.span.start, pos.span.end)) + val qual = textAt(imp.expr.srcPos) // keep original + val selector = textAt(sel.srcPos) // keep original + s"$qual.$selector" // don't succumb to vagaries of show + // begin actionable + val sortedImps = infos.imps.keySet.nn.asScala.toArray.sortBy(_.srcPos.span.point) // sorted by pos + var index = 0 + while index < sortedImps.length do + val nextImport = sortedImps.indexSatisfying(from = index + 1)(_.isPrimaryClause) // next import statement + if sortedImps.indexSatisfying(from = index, until = nextImport): imp => + imp.selectors.exists(!isUsable(imp, _)) // check if any selector in statement was unused + < nextImport then + // if no usable selectors in the import statement, delete it entirely. + // if there is exactly one usable selector, then replace with just that selector (i.e., format it). + // else for each clause, delete it or format one selector or delete unused selectors. + // To delete a comma separated item, delete start-to-start, but for last item delete a preceding comma. + // Reminder that first clause span includes the keyword, so delete point-to-start instead. + val existing = sortedImps.slice(index, nextImport) + val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList + .partition(isUsable(_, _)) + if keeping.isEmpty then + val editPos = existing.head.srcPos.sourcePos.withSpan: + Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end) + deleting.init.foreach(warnImport(_)) + warnImport(deleting.last, deletion(editPosAt(editPos, forDeletion = true))) + else if keeping.lengthIs == 1 then + val editPos = existing.head.srcPos.sourcePos.withSpan: + Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end) + deleting.init.foreach(warnImport(_)) + val text = s"import ${textFor(keeping.head)}" + warnImport(deleting.last, replace(editPosAt(editPos, forDeletion = false))(text)) + else + val lostClauses = existing.iterator.filter(imp => !keeping.exists((i, _) => imp eq i)).toList + for imp <- lostClauses do + val actions = + if imp == existing.last then + val content = imp.srcPos.sourcePos.source.content() + val prev = existing.lastIndexWhere(i0 => keeping.exists((i, _) => i == i0)) + val comma = content.indexOf(',', from = existing(prev).srcPos.span.end) + val commaPos = imp.srcPos.sourcePos.withSpan: + Span(start = comma, end = existing(prev + 1).srcPos.span.start) + val srcPos = imp.srcPos + val editPos = srcPos.sourcePos.withSpan: // exclude keyword + srcPos.span.withStart(srcPos.span.point) + actionsOf(commaPos -> "", editPos -> "") + else + val impIndex = existing.indexOf(imp) + val editPos = imp.srcPos.sourcePos.withSpan: // exclude keyword + Span(start = imp.srcPos.span.point, end = existing(impIndex + 1).srcPos.span.start) + deletion(editPos) + imp.selectors.init.foreach(sel => warnImport(imp -> sel)) + warnImport(imp -> imp.selectors.last, actions) + val singletons = existing.iterator.filter(imp => keeping.count((i, _) => imp eq i) == 1).toList + var seen = List.empty[Import] + for impsel <- deleting do + val (imp, sel) = impsel + if singletons.contains(imp) then + if seen.contains(imp) then + warnImport(impsel) + else + seen ::= imp + val editPos = imp.srcPos.sourcePos.withSpan: + Span(start = imp.srcPos.span.point, end = imp.srcPos.span.end) // exclude keyword + val text = textFor(keeping.find((i, _) => imp eq i).get) + warnImport(impsel, replace(editPosAt(editPos, forDeletion = false))(text)) + else if !lostClauses.contains(imp) then + val actions = + if sel == imp.selectors.last then + val content = sel.srcPos.sourcePos.source.content() + val prev = imp.selectors.lastIndexWhere(s0 => keeping.exists((_, s) => s == s0)) + val comma = content.indexOf(',', from = imp.selectors(prev).srcPos.span.end) + val commaPos = sel.srcPos.sourcePos.withSpan: + Span(start = comma, end = imp.selectors(prev + 1).srcPos.span.start) + val editPos = sel.srcPos + actionsOf(commaPos -> "", editPos -> "") + else + val selIndex = imp.selectors.indexOf(sel) + val editPos = sel.srcPos.sourcePos.withSpan: + sel.srcPos.span.withEnd(imp.selectors(selIndex + 1).srcPos.span.start) + deletion(editPos) + warnImport(impsel, actions) + end if + index = nextImport + end while + + warnings.result().sortBy(_._2.span.point) + end warnings + + // Specific exclusions + def ignoreTree(tree: Tree): Boolean = + tree.hasAttachment(ForArtifact) || tree.hasAttachment(Ignore) + + // The RHS of a def is too trivial to warn about unused params, e.g. def f(x: Int) = ??? + def isUnconsuming(rhs: Tree)(using Context): Boolean = + rhs.symbol == defn.Predef_undefined + || rhs.tpe =:= defn.NothingType + || rhs.isInstanceOf[Literal] + || rhs.tpe.match + case ConstantType(_) => true + case tp: TermRef => tp.underlying.classSymbol.is(Module) // Scala 2 SingleType + case _ => false + //|| isPurePath(rhs) // a bit strong + || rhs.match + case Block((dd @ DefDef(anonfun, paramss, _, _)) :: Nil, Closure(Nil, Ident(nm), _)) => + isAnonymousFunctionName(anonfun) + && anonfun == nm + && paramss.match + case (ValDef(contextual, _, _) :: Nil) :: Nil => + contextual.is(ContextFunctionParamName) + && isUnconsuming(dd.rhs) + case _ => false + case Block(Nil, Literal(u)) => u.tpe =:= defn.UnitType + case This(_) => true + case Ident(_) => rhs.symbol.is(ParamAccessor) + case Typed(rhs, _) => isUnconsuming(rhs) + case _ => false + + def absolveVariableBindings(ok: List[Name], args: List[Tree]): Unit = + ok.zip(args).foreach: (param, arg) => + arg match + case Bind(`param`, _) => arg.withAttachment(NoWarn, ()) + case _ => + + // NoWarn Binds if the name matches a "canonical" name, e.g. case element name + val nowarner = new TreeTraverser: + def traverse(tree: Tree)(using Context) = tree match + case UnApply(fun, _, args) => + def untuple = defn.PairClass.companionModule.requiredMethod("unapply") + val unapplied = tree.tpe.finalResultType.dealias.typeSymbol + if unapplied.is(CaseClass) then + absolveVariableBindings(unapplied.primaryConstructor.info.firstParamNames, args) + else if fun.symbol == untuple then + val ok = fun.symbol.info match + case PolyType(tycon, MethodTpe(_, _, AppliedType(_, tprefs))) => + tprefs.collect: + case ref: TypeParamRef => termName(ref.binder.paramNames(ref.paramNum).toString.toLowerCase.nn) + case _ => Nil + absolveVariableBindings(ok, args) + else if fun.symbol == defn.TypeTest_unapply then + () // just recurse into args + /* + fun match + case Select(qual, nme.unapply) => + qual.tpe.underlying.finalResultType match + case AppliedType(tycon, args) if tycon.typeSymbol == defn.TypeTestClass => + val target = args(1).dealias.typeSymbol + if target.is(CaseClass) then + absolveVariableBindings(target.primaryConstructor.info.firstParamNames, args) + case _ => + case _ => + */ else - Nil - val warnings = - sortedImp ::: - sortedLocalDefs ::: - sortedExplicitParams ::: - sortedImplicitParams ::: - sortedPrivateDefs ::: - sortedPatVars ::: - unsetLocalDefs ::: - unsetPrivateDefs - UnusedResult(warnings.toSet) - end getUnused - //============================ HELPERS ==================================== - - - /** - * Checks if import selects a def that is transparent and inline - */ - private def isTransparentAndInline(imp: tpd.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)) - } - - /** - * Heuristic to detect synthetic suffixes in names of symbols + val Quotes_reflect: Symbol = defn.QuotesClass.requiredClass("reflectModule") + if unapplied.exists && unapplied.owner == Quotes_reflect then + // cheapy search for parameter names via java reflection of Trees + // in lieu of drilling into requiredClass("scala.quoted.runtime.impl.QuotesImpl") + // ...member("reflect")...member(unapplied.name.toTypeName) + // with aliases into requiredModule("dotty.tools.dotc.ast.tpd") + val implName = s"dotty.tools.dotc.ast.Trees$$${unapplied.name}" + try + import scala.language.unsafeNulls + val clz = Class.forName(implName) + val ok = clz.getConstructors.head.getParameters.map(p => termName(p.getName)).toList.init + absolveVariableBindings(ok, args) + catch case _: ClassNotFoundException => () + args.foreach(traverse) + case tree => traverseChildren(tree) + + extension (nm: Name) + inline def exists(p: Name => Boolean): Boolean = nm.ne(nme.NO_NAME) && p(nm) + inline def isWildcard: Boolean = nm == nme.WILDCARD || nm.is(WildcardParamName) + + 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 + + private val serializationNames: Set[TermName] = + Set("readResolve", "readObject", "readObjectNoData", "writeObject", "writeReplace").map(termName(_)) + + extension (sym: Symbol) + def isSerializationSupport(using Context): Boolean = + sym.is(Method) && serializationNames(sym.name.toTermName) && sym.owner.isClass + && sym.owner.derivesFrom(defn.JavaSerializableClass) + def isCanEqual(using Context): Boolean = + sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) + def isMarkerTrait(using Context): Boolean = + sym.isClass && sym.info.allMembers.forall: d => + val m = d.symbol + !m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass) + + 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 as _` + * because `sel.isUnimport` is too broad for old style `import concurrent._`. */ - private def containsSyntheticSuffix(symbol: Symbol)(using Context): Boolean = - symbol.name.mangledString.contains("$") - - /** - * Is 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) + def isImportExclusion: Boolean = sel.renamed match + case untpd.Ident(nme.WILDCARD) => true + case _ => false - /** - * 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) + extension (imp: Import) + /** Is it the first import clause in a statement? `a.x` in `import a.x, b,{y, z}` */ + def isPrimaryClause(using Context): Boolean = + val span = imp.srcPos.span + span.start != span.point // primary clause starts at `import` keyword - /** - * 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 + /** Generated import of cases from enum companion. */ + def isGeneratedByEnum(using Context): Boolean = + imp.symbol.exists && imp.symbol.owner.is(Enum, butNot = Case) - /** - * If -Wunused:strict-no-implicit-warn import and this import selector could potentially import implicit. - * return true + /** Under -Wunused:strict-no-implicit-warn, avoid false positives + * if this selector is a wildcard that might import implicits or + * specifically does import an implicit. + * Similarly, import of CanEqual must not warn, as it is always witness. */ - private def shouldSelectorBeReported(imp: tpd.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 - */ - 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)))) + def isLoose(sel: ImportSelector)(using Context): Boolean = ctx.settings.WunusedHas.strictNoImplicitWarn && ( + sel.isWildcard + || imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit)) + || imp.expr.tpe.member(sel.name.toTypeName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit)) + ) || ( + sel.isWildcard && sel.isGiven + && imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual) + || imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual) + ) - /** - * Ignore definitions of CanEqual given - */ - private def isDefIgnored(memDef: tpd.MemberDef)(using Context): Boolean = - memDef.symbol.isOneOf(GivenOrImplicit) && memDef.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) - - extension (tree: ImportSelector) - def boundTpe: Type = tree.bound match { - case untpd.TypedSplice(tree1) => tree1.tpe - case _ => NoType - } - - 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 = - assert(sym.exists, s"Symbol $sym does not exist") - - 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.excludedMembers.contains(altName.getOrElse(sym.name).toTermName) then - // Wildcard with exclusions that match the symbol - false - else 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 - sym.isOneOf(Given | Implicit) - && (selector.bound.isEmpty || sym.info.finalResultType <:< selector.boundTpe) - else - // Normal wildcard, check that the symbol is not a given (but can be implicit) - !sym.is(Given) - end if - end isInImport - - /** 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) || // is a trivial def - owner.isPrimaryConstructor || - owner.annotations.exists ( // @depreacated - _.symbol == ctx.definitions.DeprecatedAnnot - ) || - owner.isAllOf(Synthetic | PrivateLocal) || - owner.is(Accessor) || - owner.isOverriden - else - false - - private def usedDefContains(using Context): Boolean = - sym.everySymbol.exists(usedDef.apply) - - 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 = - sym.is(Flags.Override) || (sym.exists && sym.owner.thisType.parents.exists(p => sym.matchingMember(p).exists)) - - end extension - - 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.exists - && !memDef.symbol.isUnusedAnnot - && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) - && !memDef.name.isWildcard - && !memDef.symbol.owner.is(ExtensionMethod) - - private def isValidParam(using Context): Boolean = - val sym = memDef.symbol - (sym.is(Param) || sym.isAllOf(PrivateParamAccessor | Local, butNot = CaseAccessor)) && - !isSyntheticMainParam(sym) && - !sym.shouldNotReportParamOwner - - private def shouldReportPrivateDef(using Context): Boolean = - currScopeType.top == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor) - - private def isUnsetVarDef(using Context): Boolean = - val sym = memDef.symbol - sym.is(Mutable) && !setVars(sym) - - extension (imp: tpd.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) - - end UnusedData - - private object UnusedData: - enum ScopeType: - case Local - case Template - case ReplWrapper - case Other - - 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 - case _ => Other - - final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): - private var myUsed: Boolean = false - var excludedMembers: Set[TermName] = Set.empty - - def markUsed(): Unit = myUsed = true - - def isUsed: Boolean = myUsed - - def markExcluded(excluded: Set[TermName]): Unit = excludedMembers ++= excluded - - private var myAllSymbols: Set[Symbol] | Null = null - - def allSymbolsForNamed(using Context): Set[Symbol] = - if myAllSymbols == null then - val allDenots = qualTpe.member(selector.name).alternatives ::: qualTpe.member(selector.name.toTypeName).alternatives - 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]) - 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 + extension (pos: SrcPos) + def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.start == pos.span.end + extension [A <: AnyRef](arr: Array[A]) + // returns `until` if not satisfied + def indexSatisfying(from: Int, until: Int = arr.length)(p: A => Boolean): Int = + var i = from + while i < until && !p(arr(i)) do + i += 1 + i end CheckUnused diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 898517806e50..b92be2041484 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -215,7 +215,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => ++ sym.annotations) else if sym.is(Param) then - sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) + // @unused is getter/setter but we want it on ordinary method params + if !sym.owner.is(Method) || sym.owner.isConstructor then + sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) else if sym.is(ParamAccessor) then // @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying` val publicInBinaryAnnotOpt = sym.getAnnotation(defn.PublicInBinaryAnnot) 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/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 76b853c4aabd..95ebecaaba23 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -84,6 +84,9 @@ object Typer { /** Indicates that an expression is explicitly ascribed to [[Unit]] type. */ val AscribedToUnit = new Property.StickyKey[Unit] + /** Tree adaptation lost fidelity; this attachment preserves the original tree. */ + val AdaptedTree = new Property.StickyKey[tpd.Tree] + /** An attachment on a Select node with an `apply` field indicating that the `apply` * was inserted by the Typer. */ @@ -3420,7 +3423,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Translate tuples of all arities */ def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree = - val tree1 = desugar.tuple(tree, pt) + val tree1 = desugar.tuple(tree, pt).withAttachmentsFrom(tree) checkDeprecatedAssignmentSyntax(tree) if tree1 ne tree then typed(tree1, pt) else @@ -4565,16 +4568,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } /** Adapt an expression of constant type to a different constant type `tpe`. */ - def adaptConstant(tree: Tree, tpe: ConstantType): Tree = { - def lit = Literal(tpe.value).withSpan(tree.span) - tree match { - case Literal(c) => lit - case tree @ Block(stats, expr) => tpd.cpy.Block(tree)(stats, adaptConstant(expr, tpe)) - case tree => - if (isIdempotentExpr(tree)) lit // See discussion in phase Literalize why we demand isIdempotentExpr - else Block(tree :: Nil, lit) - } - } + def adaptConstant(tree: Tree, tpe: ConstantType): Tree = + def lit = Literal(tpe.value).withSpan(tree.span).withAttachment(AdaptedTree, tree) + tree match + case Literal(_) => lit + case tree @ Block(stats, expr) => tpd.cpy.Block(tree)(stats, adaptConstant(expr, tpe)) + case tree => + if isIdempotentExpr(tree) then lit // See discussion in phase FirstTransform why we demand isIdempotentExpr + else Block(tree :: Nil, lit) def toSAM(tree: Tree, samParent: Type): Tree = tree match { case tree: Block => tpd.cpy.Block(tree)(tree.stats, toSAM(tree.expr, samParent)) diff --git a/compiler/src/dotty/tools/dotc/util/chaining.scala b/compiler/src/dotty/tools/dotc/util/chaining.scala new file mode 100644 index 000000000000..0c61ab6e73e9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/chaining.scala @@ -0,0 +1,8 @@ + +package dotty.tools.dotc.util + +object chaining: + + extension [A](x: A) + inline def tap(inline f: A => Unit): x.type = { f(x): Unit; x } + inline def pipe[B](inline f: A => B): B = f(x) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index f909abfc129a..3cad317d0115 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -12,7 +12,7 @@ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.reporting.Diagnostic -import dotty.tools.dotc.transform.PostTyper +import dotty.tools.dotc.transform.{CheckUnused, CheckShadowing, PostTyper} import dotty.tools.dotc.typer.ImportInfo.{withRootImports, RootRef} import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.util.Spans.* @@ -37,6 +37,7 @@ class ReplCompiler extends Compiler: List(Parser()), List(ReplPhase()), List(TyperPhase(addRootImports = false)), + List(CheckUnused.PostTyper(), CheckShadowing()), List(CollectTopLevelImports()), List(PostTyper()), ) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 2c42204a4b4d..bac7e4071c81 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -80,7 +80,8 @@ class CompilationTests { compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")), compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), - compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/ambiguous-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/unused.scala", defaultOptions.and("-rewrite", "-Wunused:all")), ).checkRewrites() } diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 29e64163b833..432ba996c27d 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -797,13 +797,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} @@ -812,58 +813,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) { @@ -998,8 +1004,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/library/src/scala/CanThrow.scala b/library/src/scala/CanThrow.scala index 91c94229c43c..913ac090c238 100644 --- a/library/src/scala/CanThrow.scala +++ b/library/src/scala/CanThrow.scala @@ -1,6 +1,6 @@ package scala import language.experimental.erasedDefinitions -import annotation.{implicitNotFound, experimental, capability} +import annotation.{implicitNotFound, experimental} /** A capability class that allows to throw exception `E`. When used with the * experimental.saferExceptions feature, a `throw Ex()` expression will require diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index 0d1deffce513..0347b66c7bf5 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -1,6 +1,6 @@ package scala import annotation.experimental -import compiletime.ops.boolean.* +//import compiletime.ops.boolean.* @experimental object NamedTuple: diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 57d1572772e2..01f4a19e5057 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -1,7 +1,6 @@ package scala import annotation.showAsInfix -import compiletime.* import compiletime.ops.int.* /** Tuple of arbitrary arity */ diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 8215ae2452a3..a98b9ee45430 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -1,7 +1,7 @@ package scala package compiletime -import annotation.{compileTimeOnly, experimental} +import annotation.compileTimeOnly /** Use this method when you have a type, do not have a value for it but want to * pattern match on it. For example, given a type `Tup <: Tuple`, one can @@ -89,10 +89,10 @@ inline def error(inline msg: String): Nothing = ??? * @syntax markdown */ transparent inline def codeOf(arg: Any): String = - // implemented in dotty.tools.dotc.typer.Inliner.Intrinsics + // implemented in dotty.tools.dotc.inlines.Inlines error("Compiler bug: `codeOf` was not evaluated by the compiler") -/** Checks at compiletime that the provided values is a constant after +/** Checks at compiletime that the provided value is a constant after * inlining and constant folding. * * Usage: @@ -108,7 +108,7 @@ transparent inline def codeOf(arg: Any): String = * @syntax markdown */ inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = - // implemented in dotty.tools.dotc.typer.Inliner + // implemented in dotty.tools.dotc.inlines.Inlines error("Compiler bug: `requireConst` was not evaluated by the compiler") /** Same as `constValue` but returns a `None` if a constant value @@ -116,21 +116,21 @@ inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | * that value wrapped in `Some`. */ transparent inline def constValueOpt[T]: Option[T] = - // implemented in dotty.tools.dotc.typer.Inliner + // implemented in dotty.tools.dotc.inlines.Inlines error("Compiler bug: `constValueOpt` was not evaluated by the compiler") /** Given a constant, singleton type `T`, convert it to a value * of the same singleton type. For example: `assert(constValue[1] == 1)`. */ transparent inline def constValue[T]: T = - // implemented in dotty.tools.dotc.typer.Inliner + // implemented in dotty.tools.dotc.inlines.Inlines error("Compiler bug: `constValue` was not evaluated by the compiler") /** Given a tuple type `(X1, ..., Xn)`, returns a tuple value * `(constValue[X1], ..., constValue[Xn])`. */ inline def constValueTuple[T <: Tuple]: T = - // implemented in dotty.tools.dotc.typer.Inliner + // implemented in dotty.tools.dotc.inlines.Inlines error("Compiler bug: `constValueTuple` was not evaluated by the compiler") @@ -177,18 +177,18 @@ transparent inline def summonInline[T]: T = * @return the given values typed as elements of the tuple */ inline def summonAll[T <: Tuple]: T = - // implemented in dotty.tools.dotc.typer.Inliner + // implemented in dotty.tools.dotc.inlines.Inlines error("Compiler bug: `summonAll` was not evaluated by the compiler") /** Assertion that an argument is by-name. Used for nullability checking. */ def byName[T](x: => T): T = x /** Casts a value to be `Matchable`. This is needed if the value's type is an unconstrained - * type parameter and the value is the scrutinee of a match expression. - * This is normally disallowed since it violates parametricity and allows - * to uncover implementation details that were intended to be hidden. - * The `asMatchable` escape hatch should be used sparingly. It's usually - * better to constrain the scrutinee type to be `Matchable` in the first place. - */ + * type parameter and the value is the scrutinee of a match expression. + * This is normally disallowed since it violates parametricity and allows + * to uncover implementation details that were intended to be hidden. + * The `asMatchable` escape hatch should be used sparingly. It's usually + * better to constrain the scrutinee type to be `Matchable` in the first place. + */ extension [T](x: T) transparent inline def asMatchable: x.type & Matchable = x.asInstanceOf[x.type & Matchable] diff --git a/library/src/scala/deriving/Mirror.scala b/library/src/scala/deriving/Mirror.scala index 57453a516567..a7477cf0fb2d 100644 --- a/library/src/scala/deriving/Mirror.scala +++ b/library/src/scala/deriving/Mirror.scala @@ -52,7 +52,7 @@ object Mirror { extension [T](p: ProductOf[T]) /** Create a new instance of type `T` with elements taken from product `a`. */ - def fromProductTyped[A <: scala.Product, Elems <: p.MirroredElemTypes](a: A)(using m: ProductOf[A] { type MirroredElemTypes = Elems }): T = + def fromProductTyped[A <: scala.Product, Elems <: p.MirroredElemTypes](a: A)(using ProductOf[A] { type MirroredElemTypes = Elems }): T = p.fromProduct(a) /** Create a new instance of type `T` with elements taken from tuple `t`. */ diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index d1385a0193d6..d254587c72d7 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -276,7 +276,7 @@ object Expr { import quotes.reflect.* Implicits.search(TypeRepr.of[T]) match { case iss: ImplicitSearchSuccess => Some(iss.tree.asExpr.asInstanceOf[Expr[T]]) - case isf: ImplicitSearchFailure => None + case _: ImplicitSearchFailure => None } } diff --git a/library/src/scala/quoted/ExprMap.scala b/library/src/scala/quoted/ExprMap.scala index fbe5dee2b342..3cdc4706e88c 100644 --- a/library/src/scala/quoted/ExprMap.scala +++ b/library/src/scala/quoted/ExprMap.scala @@ -1,5 +1,7 @@ package scala.quoted +import scala.annotation.unused + trait ExprMap: /** Map an expression `e` with a type `T` */ @@ -114,7 +116,7 @@ trait ExprMap: case _ => transformTermChildren(tree, tpe)(owner) - def transformTypeTree(tree: TypeTree)(owner: Symbol): TypeTree = tree + def transformTypeTree(tree: TypeTree)(@unused owner: Symbol): TypeTree = tree def transformCaseDef(tree: CaseDef, tpe: TypeRepr)(owner: Symbol): CaseDef = CaseDef.copy(tree)(tree.pattern, tree.guard.map(x => transformTerm(x, TypeRepr.of[Boolean])(owner)), transformTerm(tree.rhs, tpe)(owner)) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 7a98d6f6f761..ee1bd8b5b89e 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -1,7 +1,6 @@ package scala.quoted -import scala.annotation.experimental -import scala.annotation.implicitNotFound +import scala.annotation.{experimental, implicitNotFound, unused} import scala.reflect.TypeTest /** Current Quotes in scope @@ -4941,7 +4940,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => foldTree(foldTree(foldTree(x, cond)(owner), thenp)(owner), elsep)(owner) case While(cond, body) => foldTree(foldTree(x, cond)(owner), body)(owner) - case Closure(meth, tpt) => + case Closure(meth, _) => foldTree(x, meth)(owner) case Match(selector, cases) => foldTrees(foldTree(x, selector)(owner), cases)(owner) @@ -5019,7 +5018,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def traverseTree(tree: Tree)(owner: Symbol): Unit = traverseTreeChildren(tree)(owner) - def foldTree(x: Unit, tree: Tree)(owner: Symbol): Unit = traverseTree(tree)(owner) + def foldTree(@unused x: Unit, tree: Tree)(owner: Symbol): Unit = traverseTree(tree)(owner) protected def traverseTreeChildren(tree: Tree)(owner: Symbol): Unit = foldOverTree((), tree)(owner) diff --git a/library/src/scala/quoted/Type.scala b/library/src/scala/quoted/Type.scala index b035bdd6e52f..33423b86080b 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src/scala/quoted/Type.scala @@ -1,6 +1,6 @@ package scala.quoted -import scala.annotation.{compileTimeOnly, experimental} +import scala.annotation.compileTimeOnly /** Type (or type constructor) `T` needed contextually when using `T` in a quoted expression `'{... T ...}` */ abstract class Type[T <: AnyKind] private[scala]: diff --git a/library/src/scala/quoted/runtime/Expr.scala b/library/src/scala/quoted/runtime/Expr.scala index b95f225c13b3..f491ba61b382 100644 --- a/library/src/scala/quoted/runtime/Expr.scala +++ b/library/src/scala/quoted/runtime/Expr.scala @@ -1,7 +1,7 @@ package scala.quoted package runtime -import scala.annotation.{Annotation, compileTimeOnly} +import scala.annotation.compileTimeOnly @compileTimeOnly("Illegal reference to `scala.quoted.runtime.Expr`") object Expr: diff --git a/library/src/scala/reflect/Selectable.scala b/library/src/scala/reflect/Selectable.scala index ac8e502128bb..0fae68eba048 100644 --- a/library/src/scala/reflect/Selectable.scala +++ b/library/src/scala/reflect/Selectable.scala @@ -24,7 +24,7 @@ trait Selectable extends scala.Selectable: val fld = rcls.getField(NameTransformer.encode(name)).nn ensureAccessible(fld) fld.get(selectedValue) - catch case ex: NoSuchFieldException => + catch case _: NoSuchFieldException => applyDynamic(name)() // The Scala.js codegen relies on this method being final for correctness diff --git a/library/src/scala/runtime/Arrays.scala b/library/src/scala/runtime/Arrays.scala index 9f5bdd99a5f4..085b36c08a1f 100644 --- a/library/src/scala/runtime/Arrays.scala +++ b/library/src/scala/runtime/Arrays.scala @@ -1,5 +1,6 @@ package scala.runtime +import scala.annotation.unused import scala.reflect.ClassTag import java.lang.{reflect => jlr} @@ -26,6 +27,6 @@ object Arrays { /** Create an array of a reference type T. */ - def newArray[Arr](componentType: Class[?], returnType: Class[Arr], dimensions: Array[Int]): Arr = + def newArray[Arr](componentType: Class[?], @unused returnType: Class[Arr], dimensions: Array[Int]): Arr = jlr.Array.newInstance(componentType, dimensions*).asInstanceOf[Arr] } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index 15220ea2410a..78e7a9fc8d15 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -8,7 +8,7 @@ import scala.annotation.* * Helper methods used in thread-safe lazy vals. */ object LazyVals { - @nowarn + //@nowarn private val unsafe: sun.misc.Unsafe = { def throwInitializationException() = throw new ExceptionInInitializerError( @@ -33,7 +33,7 @@ object LazyVals { private val monitors: Array[Object] = Array.tabulate(base)(_ => new Object) - private def getMonitor(obj: Object, fieldId: Int = 0) = { + private def getMonitor(obj: Object, fieldId: Int) = { var id = (java.lang.System.identityHashCode(obj) + fieldId) % base if (id < 0) id += base @@ -96,13 +96,13 @@ object LazyVals { println(s"CAS($t, $offset, $e, $v, $ord)") val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL) val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL)) - unsafe.compareAndSwapLong(t, offset, e, n) + unsafe.compareAndSwapLong(t, offset, e, n): @nowarn } def objCAS(t: Object, offset: Long, exp: Object, n: Object): Boolean = { if (debug) println(s"objCAS($t, $exp, $n)") - unsafe.compareAndSwapObject(t, offset, exp, n) + unsafe.compareAndSwapObject(t, offset, exp, n): @nowarn } def setFlag(t: Object, offset: Long, v: Int, ord: Int): Unit = { @@ -147,7 +147,7 @@ object LazyVals { def get(t: Object, off: Long): Long = { if (debug) println(s"get($t, $off)") - unsafe.getLongVolatile(t, off) + unsafe.getLongVolatile(t, off): @nowarn } // kept for backward compatibility diff --git a/library/src/scala/runtime/Tuples.scala b/library/src/scala/runtime/Tuples.scala index 66dc486d2a1d..00d4d2b26233 100644 --- a/library/src/scala/runtime/Tuples.scala +++ b/library/src/scala/runtime/Tuples.scala @@ -286,7 +286,7 @@ object Tuples { // Tail for Tuple1 to Tuple22 private def specialCaseTail(self: Tuple): Tuple = { (self: Any) match { - case self: Tuple1[?] => + case _: Tuple1[?] => EmptyTuple case self: Tuple2[?, ?] => Tuple1(self._2) diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala index b3216ec37c67..2613a41a5541 100644 --- a/library/src/scala/runtime/coverage/Invoker.scala +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -5,7 +5,6 @@ import scala.annotation.nowarn import scala.collection.concurrent.TrieMap import scala.collection.mutable.{BitSet, HashMap} import java.io.{File, FileWriter} -import java.nio.file.Files @sharable // avoids false positive by -Ycheck-reentrant object Invoker { diff --git a/library/src/scala/util/CommandLineParser.scala b/library/src/scala/util/CommandLineParser.scala index fd239ef231c5..e1311df50d54 100644 --- a/library/src/scala/util/CommandLineParser.scala +++ b/library/src/scala/util/CommandLineParser.scala @@ -48,9 +48,7 @@ object CommandLineParser { def fromStringOption(s: String): Option[T] = try Some(fromString(s)) - catch { - case ex: IllegalArgumentException => None - } + catch case _: IllegalArgumentException => None } object FromString { diff --git a/library/src/scala/util/FromDigits.scala b/library/src/scala/util/FromDigits.scala index cb73782829ff..5073188852d7 100644 --- a/library/src/scala/util/FromDigits.scala +++ b/library/src/scala/util/FromDigits.scala @@ -1,6 +1,5 @@ package scala.util -import scala.math.{BigInt} -import quoted.* +import scala.math.BigInt import annotation.internal.sharable @@ -131,9 +130,7 @@ object FromDigits { def floatFromDigits(digits: String): Float = { val x: Float = try java.lang.Float.parseFloat(digits) - catch { - case ex: NumberFormatException => throw MalformedNumber() - } + catch case _: NumberFormatException => throw MalformedNumber() if (x.isInfinite) throw NumberTooLarge() if (x == 0.0f && !zeroFloat.pattern.matcher(digits).nn.matches) throw NumberTooSmall() x @@ -149,9 +146,7 @@ object FromDigits { def doubleFromDigits(digits: String): Double = { val x: Double = try java.lang.Double.parseDouble(digits) - catch { - case ex: NumberFormatException => throw MalformedNumber() - } + catch case _: NumberFormatException => throw MalformedNumber() if (x.isInfinite) throw NumberTooLarge() if (x == 0.0d && !zeroFloat.pattern.matcher(digits).nn.matches) throw NumberTooSmall() x diff --git a/project/Build.scala b/project/Build.scala index b67974f4405d..bab9418642b3 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -292,7 +292,9 @@ object Build { "-deprecation", "-unchecked", //"-Wconf:cat=deprecation&msg=Unsafe:s", // example usage - "-Xfatal-warnings", // -Werror in modern usage + "-Werror", + //"-Wunused:all", + //"-rewrite", // requires -Werror:false since no rewrites are applied with errors "-encoding", "UTF8", "-language:implicitConversions", ), @@ -1225,8 +1227,8 @@ object Build { }, Compile / doc / scalacOptions += "-Ydocument-synthetic-types", scalacOptions += "-Ycompile-scala2-library", - scalacOptions += "-Yscala2Unpickler:never", - scalacOptions -= "-Xfatal-warnings", + scalacOptions += "-Yscala2-unpickler:never", + scalacOptions += "-Werror:false", Compile / compile / logLevel.withRank(KeyRanks.Invisible) := Level.Error, ivyConfigurations += SourceDeps.hide, transitiveClassifiers := Seq("sources"), @@ -1597,7 +1599,7 @@ object Build { dependsOn(`scala3-library-bootstrappedJS`). settings( bspEnabled := false, - scalacOptions --= Seq("-Xfatal-warnings", "-deprecation"), + scalacOptions --= Seq("-Werror", "-deprecation"), // Required to run Scala.js tests. Test / fork := false, diff --git a/tasty/src/dotty/tools/tasty/TastyReader.scala b/tasty/src/dotty/tools/tasty/TastyReader.scala index b5aa29f16954..d4374a76ff99 100644 --- a/tasty/src/dotty/tools/tasty/TastyReader.scala +++ b/tasty/src/dotty/tools/tasty/TastyReader.scala @@ -100,7 +100,7 @@ class TastyReader(val bytes: Array[Byte], start: Int, end: Int, val base: Int = /** Read an uncompressed Long stored in 8 bytes in big endian format */ def readUncompressedLong(): Long = { var x: Long = 0 - for (i <- 0 to 7) + for (_ <- 0 to 7) x = (x << 8) | (readByte() & 0xff) x } diff --git a/tests/pos-macros/i18409.scala b/tests/pos-macros/i18409.scala index 800e192b81bb..d1806b1d4d83 100644 --- a/tests/pos-macros/i18409.scala +++ b/tests/pos-macros/i18409.scala @@ -1,4 +1,4 @@ -//> using options -Werror -Wunused:all +//> using options -Werror -Wunused:imports import scala.quoted.* diff --git a/tests/pos/i11729.scala b/tests/pos/i11729.scala index e97b285ac6a2..79d3174dc2e9 100644 --- a/tests/pos/i11729.scala +++ b/tests/pos/i11729.scala @@ -6,7 +6,7 @@ type Return[X] = X match object Return: def apply[A](a:A):Return[A] = a match - case a: List[t] => a + case a: List[?] => a case a: Any => List(a) object Test1: @@ -18,7 +18,7 @@ type Boxed[X] = X match case Any => Box[X] def box[X](x: X): Boxed[X] = x match - case b: Box[t] => b + case b: Box[?] => b case x: Any => Box(x) case class Box[A](a:A): 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/pos/i3323.scala b/tests/pos/i3323.scala deleted file mode 100644 index 94d072d4a2fc..000000000000 --- a/tests/pos/i3323.scala +++ /dev/null @@ -1,9 +0,0 @@ -//> using options -Xfatal-warnings -deprecation -feature - -class Foo { - def foo[A](lss: List[List[A]]): Unit = { - lss match { - case xss: List[List[A]] => - } - } -} diff --git a/tests/pos/patmat-exhaustive.scala b/tests/pos/patmat-exhaustive.scala index 9e3cb7d8f615..a8f057664829 100644 --- a/tests/pos/patmat-exhaustive.scala +++ b/tests/pos/patmat-exhaustive.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -deprecation -feature +//> using options -Werror -deprecation -feature def foo: Unit = object O: @@ -8,5 +8,5 @@ def foo: Unit = val x: O.A = ??? x match - case x: B => ??? - case x: C => ??? + case _: B => ??? + case _: C => ??? diff --git a/tests/pos/tuple-exaustivity.scala b/tests/pos/tuple-exaustivity.scala deleted file mode 100644 index a27267fc89e5..000000000000 --- a/tests/pos/tuple-exaustivity.scala +++ /dev/null @@ -1,6 +0,0 @@ -//> using options -Xfatal-warnings -deprecation -feature - -def test(t: Tuple) = - t match - case Tuple() => - case head *: tail => diff --git a/tests/pos/tuple-exhaustivity.scala b/tests/pos/tuple-exhaustivity.scala new file mode 100644 index 000000000000..9060d112b197 --- /dev/null +++ b/tests/pos/tuple-exhaustivity.scala @@ -0,0 +1,6 @@ +//> using options -Werror -deprecation -feature + +def test(t: Tuple) = + t match + case Tuple() => + case h *: t => diff --git a/tests/rewrites/ambigious-named-tuple-assignment.check b/tests/rewrites/ambiguous-named-tuple-assignment.check similarity index 100% rename from tests/rewrites/ambigious-named-tuple-assignment.check rename to tests/rewrites/ambiguous-named-tuple-assignment.check diff --git a/tests/rewrites/ambigious-named-tuple-assignment.scala b/tests/rewrites/ambiguous-named-tuple-assignment.scala similarity index 100% rename from tests/rewrites/ambigious-named-tuple-assignment.scala rename to tests/rewrites/ambiguous-named-tuple-assignment.scala diff --git a/tests/rewrites/unused.check b/tests/rewrites/unused.check new file mode 100644 index 000000000000..1ff93bfb6ef2 --- /dev/null +++ b/tests/rewrites/unused.check @@ -0,0 +1,55 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p2: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p3: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p4: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p5: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p6: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p7: + import java.lang.{Runnable, + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p8: + import java.lang.{Runnable as R, System}, System.out + class C extends R { def run() = out.println() } + +package p9: + import java.lang.{Runnable as R, System, + Thread} + class C extends R { def run() = Thread(() => System.out.println()).start() } + +package p10: + object X: + def x = 42 + class C: + import X.* // preserve text, don't rewrite to p10.X.* + def c = x + +package p11: + import collection.mutable, mutable.ListBuffer // warn // warn // not checked :( + def buf = ListBuffer.empty[String] + +package p12: + import java.lang.System, java.lang.Runnable + class C extends Runnable { def run() = System.out.println() } diff --git a/tests/rewrites/unused.scala b/tests/rewrites/unused.scala new file mode 100644 index 000000000000..85a83c7c0015 --- /dev/null +++ b/tests/rewrites/unused.scala @@ -0,0 +1,61 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + import java.lang.{Thread, String}, java.lang.Integer + class C extends Runnable { def run() = () } + +package p2: + import java.lang.Thread + import java.lang.String + import java.lang.Runnable + import java.lang.Integer + class C extends Runnable { def run() = () } + +package p3: + import java.lang.{Runnable, Thread, String} + class C extends Runnable { def run() = () } + +package p4: + import java.lang.{Runnable, System, Thread}, System.out + class C extends Runnable { def run() = out.println() } + +package p5: + import java.lang.{Thread, Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p6: + import java.lang.{Runnable, System}, java.lang.Thread, System.out + class C extends Runnable { def run() = out.println() } + +package p7: + import java.lang.{Runnable, + Thread, + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p8: + import java.lang.{Runnable as R, System, + Thread}, System.out + class C extends R { def run() = out.println() } + +package p9: + import java.lang.{Runnable as R, System, + Thread}, System.out + class C extends R { def run() = Thread(() => System.out.println()).start() } + +package p10: + object X: + def x = 42 + class C: + import X.*, java.util.HashMap // preserve text, don't rewrite to p10.X.* + def c = x + +package p11: + import collection.mutable, mutable.ListBuffer, java.lang.{Runnable, Thread} // warn // warn // not checked :( + def buf = ListBuffer.empty[String] + +package p12: + import collection.mutable, java.lang.System, java.lang.Runnable + class C extends Runnable { def run() = System.out.println() } diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 26221899035b..6ab38e52a8ed 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 => 11 entries Synthetics => 2 entries Symbols: @@ -818,6 +818,7 @@ Occurrences: [53:10..53:11): + -> scala/Int#`+`(+4). Diagnostics: +[13:20..13:21): [warning] unused explicit parameter [18:9..18:10): [warning] unused explicit parameter [20:23..20:23): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. @@ -830,6 +831,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] @@ -2095,6 +2100,7 @@ Text => empty Language => Scala Symbols => 45 entries Occurrences => 66 entries +Diagnostics => 1 entries Synthetics => 3 entries Symbols: @@ -2212,6 +2218,9 @@ Occurrences: [41:8..41:17): given_Z_T -> givens/InventedNames$package.given_Z_T(). [41:18..41:24): String -> scala/Predef.String# +Diagnostics: +[24:13..24:13): [warning] unused implicit parameter + Synthetics: [24:0..24:0): => *(x$1) [34:8..34:20):given_Double => *(intValue) @@ -3533,6 +3542,7 @@ Text => empty Language => Scala Symbols => 62 entries Occurrences => 165 entries +Diagnostics => 3 entries Synthetics => 39 entries Symbols: @@ -3766,6 +3776,11 @@ Occurrences: [68:18..68:24): impure -> local20 [68:30..68:31): s -> local16 +Diagnostics: +[19:21..19:22): [warning] unused pattern variable +[41:4..41:5): [warning] unused pattern variable +[63:10..63:11): [warning] unused explicit parameter + Synthetics: [5:2..5:13):List(1).map => *[Int] [5:2..5:6):List => *.apply[Int] @@ -3927,7 +3942,6 @@ Text => empty Language => Scala Symbols => 22 entries Occurrences => 45 entries -Diagnostics => 3 entries Synthetics => 11 entries Symbols: @@ -4001,11 +4015,6 @@ Occurrences: [39:10..39:17): leftVar -> local3 [40:10..40:18): rightVar -> local4 -Diagnostics: -[30:11..30:18): [warning] unset local variable, consider using an immutable val instead -[30:20..30:28): [warning] unset local variable, consider using an immutable val instead -[31:15..31:25): [warning] unset local variable, consider using an immutable val instead - Synthetics: [5:6..5:10):Some => *.unapply[Int] [6:4..6:8):Some => *.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 ---------------------- @@ -4620,6 +4634,7 @@ Text => empty Language => Scala Symbols => 24 entries Occurrences => 63 entries +Diagnostics => 2 entries Symbols: _empty_/Copy# => trait Copy [typeparam In <: Txn[In], typeparam Out <: Txn[Out]] extends Object { self: Copy[In, Out] => +5 decls } @@ -4712,6 +4727,10 @@ Occurrences: [14:8..14:15): println -> scala/Predef.println(+1). [17:4..17:7): out -> local0 +Diagnostics: +[13:12..13:17): [warning] unused pattern variable +[13:28..13:34): [warning] unused pattern variable + expect/inlineconsume.scala -------------------------- @@ -5006,7 +5025,7 @@ Text => empty Language => Scala Symbols => 50 entries Occurrences => 78 entries -Diagnostics => 4 entries +Diagnostics => 6 entries Synthetics => 2 entries Symbols: @@ -5146,6 +5165,8 @@ Diagnostics: [9:36..9:37): [warning] unused explicit parameter [9:42..9:43): [warning] unused explicit parameter [21:11..21:12): [warning] unused explicit parameter +[24:24..24:27): [warning] unused pattern variable +[25:27..25:28): [warning] unused pattern variable Synthetics: [23:6..23:10):List => *.unapplySeq[Nothing] @@ -5558,13 +5579,13 @@ Occurrences: [119:39..119:42): Int -> scala/Int# Diagnostics: -[5:13..5:14): [warning] unused explicit parameter [62:25..62:29): [warning] with as a type operator has been deprecated; use & instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. [63:25..63:29): [warning] with as a type operator has been deprecated; use & instead 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/pos/i15226.scala b/tests/warn/i15226.scala similarity index 90% rename from tests/pos/i15226.scala rename to tests/warn/i15226.scala index dfb61efc6a8c..2218b4bdbd1b 100644 --- a/tests/pos/i15226.scala +++ b/tests/warn/i15226.scala @@ -1,4 +1,3 @@ -//> using options -Werror class Proj { type State = String } sealed trait ProjState: diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index df8691c21a13..40b6c75983bf 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -1,5 +1,4 @@ -//> using options -Wunused:imports - +//> using options -Wunused:imports -Wconf:origin=Suppressed.*:s object FooUnused: import collection.mutable.Set // warn @@ -68,7 +67,7 @@ object InlineChecks: inline def getSet = Set(1) object InlinedBar: - import collection.mutable.Set // ok + import collection.mutable.Set // warn (don't be fooled by inline expansion) import collection.mutable.Map // warn val a = InlineFoo.getSet @@ -100,20 +99,28 @@ object SomeGivenImports: given String = "foo" /* BEGIN : Check on packages*/ -package testsamepackageimport: - package p { +package nestedpackageimport: + package p: class C - } - - package p { - import p._ // warn - package q { - class U { + package p: + package q: + import p.* // warn + class U: def f = new C - } - } - } -// ----------------------- +package unnestedpackageimport: + package p: + class C + package p.q: + import p.* // nowarn + class U: + def f = new C + +package redundancy: + object redundant: + def f = 42 + import redundancy.* // warn superseded by def in scope + class R: + def g = redundant.f package testpackageimport: package a: @@ -213,7 +220,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 +273,51 @@ 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] + +object Selections: + def f(list: List[Int]): Int = + import list.{head => first} // OK + first + + def f2(list: List[Int]): Int = + import list.head // OK + head + + def f3(list: List[Int]): Int = + import list.head // warn + list.head + + object N: + val ns: List[Int] = Nil + + def g(): Int = + import N.ns // OK + ns.head +end Selections + +object `more nestings`: + object Outer: + object Inner: + val thing = 42 + def j() = + import Inner.thing // warn + thing + def k() = + import Inner.thing // warn + Inner.thing + + object Thing: + object Inner: + val thing = 42 + import Inner.thing // warn + def j() = + thing + def k() = + Inner.thing + +object Suppressed: + val suppressed = 42 +object Suppressing: + import Suppressed.* // no warn, see options + def f = 42 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/i15503c.scala b/tests/warn/i15503c.scala index a813329da89b..86b972487e17 100644 --- a/tests/warn/i15503c.scala +++ b/tests/warn/i15503c.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:privates -source:3.3 +//> using options -Wunused:privates -source:3.3 trait C class A: @@ -33,17 +33,22 @@ class A: var w = 2 // OK package foo.test.constructors: - case class A private (x:Int) // OK + case class A private (x: Int) // OK class B private (val x: Int) // OK - class C private (private val x: Int) // warn + object B { def default = B(42) } + class C private (private val xy: Int) // warn + object C { def default = C(42) } class D private (private val x: Int): // OK def y = x + object D { def default = D(42) } class E private (private var x: Int): // warn not set def y = x + object E { def default = E(42) } class F private (private var x: Int): // OK def y = x = 3 x + object F { def default = F(42) } package test.foo.i16682: object myPackage: @@ -55,4 +60,13 @@ package test.foo.i16682: case _ => println("NaN") } - def f = myPackage.isInt("42") \ No newline at end of file + def f = myPackage.isInt("42") + +object LazyVals: + import java.util.concurrent.CountDownLatch + + // This trait extends Serializable to fix #16806 that caused a race condition + sealed trait LazyValControlState extends Serializable + + final class Waiting extends CountDownLatch(1), LazyValControlState: + private def writeReplace(): Any = null diff --git a/tests/warn/i15503d.scala b/tests/warn/i15503d.scala index 9a0ba9b2f8dc..494952e2e4b0 100644 --- a/tests/warn/i15503d.scala +++ b/tests/warn/i15503d.scala @@ -1,5 +1,6 @@ -//> using options -Wunused:unsafe-warn-patvars -// todo : change to :patvars +//> using options -Wunused:patvars + +import scala.reflect.Typeable sealed trait Calc sealed trait Const extends Calc @@ -8,23 +9,118 @@ 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 // warn + case Sum(x,Z) => Z // warn // case Sum(a @ _,Z) => Z // todo : this should pass in the future - case Sum(a@S(_),Z) => Z // warn - case Sum(a@S(_),Z) => a // warn unreachable - case Sum(a@S(b@S(_)), Z) => a // warn - case Sum(a@S(b@S(_)), Z) => a // warn - case Sum(a@S(b@(S(_))), Z) => Sum(a,b) // warn unreachable + case Sum(x@S(_),Z) => Z // warn + case Sum(x@S(_),Z) => x // warn unreachable + case Sum(x@S(y@S(_)), Z) => x // warn + case Sum(x@S(y@S(_)), Z) => x // warn + case Sum(x@S(y@(S(_))), Z) => Sum(x,y) // warn unreachable case Sum(_,_) => Z // OK case _ => Z // warn unreachable } -// todo : This should pass in the future -// val b = for { -// case Some(x) <- Option(Option(1)) -// } println(s"$x") +case class K(i: Int, j: Int) + +class C(c0: Option[Int], k0: K): + private val Some(c) = c0: @unchecked // warn valdef from pattern + private val K(i, j) = k0 // warn // warn valdefs from pattern (RHS patvars are NoWarn) + val K(v, w) = k0 // nowarn nonprivate + private val K(r, s) = k0 // warn // warn valdefs from pattern + def f(x: Option[Int]) = x match + case Some(y) => true // warn Bind in pattern + case _ => false + def g(ns: List[Int]) = + for x <- ns do println() // warn valdef function param from for + def g1(ns: List[Int]) = + for x <- ns do println(x) // x => println(x) + def h(ns: List[Int]) = + for x <- ns; y = x + 1 // warn tupling from for; x is used, y is unused + do println() + def k(x: Option[K]) = + x match + case Some(K(i, j)) => // nowarn canonical names + case _ => + + val m = Map( + "first" -> Map((true, 1), (false, 2), (true, 3)), + "second" -> Map((true, 1), (false, 2), (true, 3)), + ) + def guardedUse = + m.map: (a, m1) => + for (status, lag) <- m1 if status + yield (a, status, lag) + def guardedUseOnly = + m.map: (a, m1) => + for (status, lag) <- m1 if status + yield (a, lag) + def guardedUseMissing = + m.map: (a, m1) => + for (status, lag) <- m1 // warn + yield (a, lag) + def flatGuardedUse = + for (a, m1) <- m; (status, lag) <- m1 if status + yield (a, status, lag) + def leading = + for _ <- List("42"); i = 1; _ <- List("0", "27")(i) + yield () + def optional = + for case Some(x) <- List(Option(42)) + yield x + def nonoptional = + for case Some(x) <- List(Option(42)) // warn + yield 27 + def optionalName = + for case Some(value) <- List(Option(42)) + yield 27 + + /* + def tester[A](a: A)(using Typeable[K]) = + a match + case S(i, j) => i + j + case _ => 0 + */ + +class Wild: + def f(x: Any) = + x match + case _: Option[?] => true + case _ => false + +def untuple(t: Tuple) = + t match + case Tuple() => + case h *: t => // no warn canonical names taken from tuple element types, (Head, Tail) -> (head, tail) + //case head *: tail => // no warn canonical names taken from tuple element types, (Head, Tail) -> (head, tail) + +// empty case class: +// def equals(other) = other match { case other => true } // exonerated name +object i15967: + sealed trait A[-Z] + final case class B[Y]() extends A[Y] + +object `patvar is assignable`: + var (i, j) = (42, 27) // no warn nonprivate + j += 1 + println((i, j)) + +object `privy patvar is assignable`: + private var (i, j) = (42, 27) // warn + j += 1 + println((i, j)) + +object `local patvar is assignable`: + def f() = + var (i, j) = (42, 27) // warn + j += 1 + println((i, j)) + +object `mutable patvar in for`: + def f(xs: List[Int]) = + for x <- xs; y = x + 1 if y > 10 yield + var z :: Nil = y :: Nil: @unchecked // warn + z + 10 -// todo : This should *NOT* pass in the future -// val c = for { -// case Some(x) <- Option(Option(1)) -// } println(s"hello world") \ No newline at end of file +class `unset var requires -Wunused`: + private var i = 0 // no warn as we didn't ask for it + def f = println(i) diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index 46d73a4945cd..2fafec339ac1 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -1,4 +1,6 @@ -//> using options -Wunused:explicits +//> using options -Wunused:explicits + +import annotation.* object Foo { /* This goes around the "trivial method" detection */ @@ -31,16 +33,17 @@ 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: /* A twisted test from Scala 2 */ - class C { + class C(val value: Int) { def answer: 42 = 42 object X private def g0(x: Int) = ??? // OK private def f0(x: Int) = () // OK + private def f00(x: Int) = {} // OK private def f1(x: Int) = throw new RuntimeException // OK private def f2(x: Int) = 42 // OK private def f3(x: Int): Option[Int] = None // OK @@ -50,13 +53,16 @@ package foo.test.trivial: private def f7(x: Int) = Y // OK private def f8(x: Int): List[C] = Nil // OK private def f9(x: Int): List[Int] = List(1,2,3,4) // warn - private def foo:Int = 32 // OK + private def foo: Int = 32 // OK private def f77(x: Int) = foo // warn + private def self(x: Int): C = this // no warn + private def unwrap(x: Int): Int = value // no warn } object Y package foo.test.i16955: - class S(var r: String) // OK + class S(var rrr: String) // OK + class T(rrr: String) // warn package foo.test.i16865: trait Foo: @@ -64,7 +70,26 @@ 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 + +final class alpha(externalName: String) extends StaticAnnotation // no warn annotation arg + +object Unimplemented: + import compiletime.* + inline def f(inline x: Int | Double): Unit = error("unimplemented") // no warn param of trivial method + +def `trivially wrapped`(x: String): String ?=> String = "hello, world" // no warn param of trivial method + +object UnwrapTyped: + import compiletime.error + inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = + error("Compiler bug: `requireConst` was not evaluated by the compiler") + + transparent inline def codeOf(arg: Any): String = + error("Compiler bug: `codeOf` was not evaluated by the compiler") + +object `default usage`: + def f(i: Int)(j: Int = i * 2) = j // warn I guess diff --git a/tests/warn/i15503f.scala b/tests/warn/i15503f.scala index ccf0b7e74065..a03d0926e658 100644 --- a/tests/warn/i15503f.scala +++ b/tests/warn/i15503f.scala @@ -6,9 +6,46 @@ 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 + private def f9(a: Int)(using Int) = ??? // OK trivial + private def g1(a: Int)(implicit foo: Int) = a // warn } + +trait T +object T: + def hole(using T) = () + +class C(using T) // warn + +class D(using T): + def t = T.hole // nowarn + +object Example: + import scala.quoted.* + given OptionFromExpr[T](using Type[T], FromExpr[T]): FromExpr[Option[T]] with + def unapply(x: Expr[Option[T]])(using Quotes) = x match + case '{ Option[T](${Expr(y)}) } => Some(Option(y)) + case '{ None } => Some(None) + case '{ ${Expr(opt)} : Some[T] } => Some(opt) + case _ => None + given OptionFromExprNoisy[T](using Type[T], FromExpr[T]): FromExpr[Option[T]] with + def unapply(x: Expr[Option[T]])(using Quotes) = x match + case '{ Option[T](${Expr(y)}) } => Some(Option(y)) + case '{ None } => Some(None) + //case '{ ${Expr(opt)} : Some[T] } => Some(opt) // make Type param unused after typer + case _ => None + +//absolving names on matches of quote trees requires consulting non-abstract types in QuotesImpl +object Unmatched: + import scala.quoted.* + def transform[T](e: Expr[T])(using Quotes): Expr[T] = + import quotes.reflect.* + def f(tree: Tree) = + tree match + case Ident(name) => + case _ => + e diff --git a/tests/warn/i15503g.scala b/tests/warn/i15503g.scala index fbd9f3c1352c..cfbfcdb04d1e 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,5 @@ 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 + def isAnIssue(y: A): Boolean = x == x // warn + } 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..1fb741fd5745 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 @@ -29,11 +31,15 @@ class A { 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 + def g(x: Int): Int = x match + case x: 1 => 0 // no warn same name as selector (for shadowing or unused) + case x: 2 => x // OK + case _ => 1 // OK + + def h(x: Int): Int = x match + case y: 1 => 0 // warn unused despite trivial type and RHS + case y: 2 => y // OK + case _ => 1 // OK } /* ---- CHECK scala.annotation.unused ---- */ @@ -44,7 +50,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 +88,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: @@ -91,7 +97,7 @@ package foo.test.possibleclasses: k: Int, // OK private val y: Int // OK /* Kept as it can be taken from pattern */ )( - s: Int, + s: Int, // warn val t: Int, // OK private val z: Int // warn ) @@ -130,22 +136,22 @@ package foo.test.possibleclasses: package foo.test.possibleclasses.withvar: case class AllCaseClass( k: Int, // OK - private var y: Int // OK /* Kept as it can be taken from pattern */ + private var y: Int // warn unset )( - s: Int, - var t: Int, // OK - private var z: Int // warn + s: Int, // warn + var ttt: Int, // OK + private var zzz: Int // warn ) case class AllCaseUsed( k: Int, // OK - private var y: Int // OK + private var y: Int // warn unset )( s: Int, // OK - var t: Int, // OK global scope can be set somewhere else - private var z: Int // warn not set + var tt: Int, // OK global scope can be set somewhere else + private var zz: Int // warn not set ) { - def a = k + y + s + t + z + def a = k + y + s + tt + zz } class AllClass( @@ -199,14 +205,14 @@ package foo.test.i16877: package foo.test.i16926: def hello(): Unit = for { - i <- (0 to 10).toList + i <- (0 to 10).toList // warn patvar (a, b) = "hello" -> "world" // OK } yield println(s"$a $b") package foo.test.i16925: def hello = for { - i <- 1 to 2 if true + i <- 1 to 2 if true // OK _ = println(i) // OK } yield () @@ -247,7 +253,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 +269,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 +285,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/i15503k.scala b/tests/warn/i15503k.scala new file mode 100644 index 000000000000..8148de44c588 --- /dev/null +++ b/tests/warn/i15503k.scala @@ -0,0 +1,43 @@ + +//> using options -Wunused:imports + +import scala.compiletime.ops.int.* // no warn + +object TupleOps: + /** Type of the element at position N in the tuple X */ + type Elem[X <: Tuple, N <: Int] = X match { + case x *: xs => + N match { + case 0 => x + case S[n1] => Elem[xs, n1] + } + } + + /** Literal constant Int size of a tuple */ + type Size[X <: Tuple] <: Int = X match { + case EmptyTuple => 0 + case x *: xs => S[Size[xs]] + } + +object Summoner: + transparent inline def summoner[T](using x: T): x.type = x + +object `Summoner's Tale`: + import compiletime.summonFrom // no warn + inline def valueOf[T]: T = summonFrom: // implicit match + case ev: ValueOf[T] => ev.value + import Summoner.* // no warn + def f[T](using T): T = summoner[T] // Inlined + +class C: + private def m: Int = 42 // no warn +object C: + class D: + private val c: C = C() // no warn + export c.m // no work to do, expanded member is non-private and uses the select expr + +object UsefulTypes: + trait T +object TypeUser: + import UsefulTypes.* + def f(x: => T) = x diff --git a/tests/warn/i15503kb/power.scala b/tests/warn/i15503kb/power.scala new file mode 100644 index 000000000000..f4e17fc5a909 --- /dev/null +++ b/tests/warn/i15503kb/power.scala @@ -0,0 +1,15 @@ + +object Power: + import scala.concurrent.* // warn [taps mic] + import scala.math.pow as power + import scala.quoted.* + inline def powerMacro(x: Double, inline n: Int): Double = ${ powerCode('x, 'n) } + def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + n match + case Expr(m) => unrolledPowerCode(x, m) + case _ => '{ power($x, $n.toDouble) } + def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + n match + case 0 => '{ 1.0 } + case 1 => x + case _ => '{ $x * ${ unrolledPowerCode(x, n - 1) } } diff --git a/tests/warn/i15503kb/square.scala b/tests/warn/i15503kb/square.scala new file mode 100644 index 000000000000..cf9e135dc9a0 --- /dev/null +++ b/tests/warn/i15503kb/square.scala @@ -0,0 +1,6 @@ +//> using options -Wunused:all + +object PowerUser: + import scala.concurrent.* // warn [taps mic] + import Power.* + def square(x: Double): Double = powerMacro(x, 2) diff --git a/tests/pos/i15967.scala b/tests/warn/i15967.scala similarity index 90% rename from tests/pos/i15967.scala rename to tests/warn/i15967.scala index 1bf03a87cdd4..8682008cacf9 100644 --- a/tests/pos/i15967.scala +++ b/tests/warn/i15967.scala @@ -1,4 +1,3 @@ -//> using options -Werror sealed trait A[-Z] final case class B[Y]() extends A[Y] diff --git a/tests/warn/i16639a.scala b/tests/warn/i16639a.scala index 9fe4efe57d7b..131b2a6db2b0 100644 --- a/tests/warn/i16639a.scala +++ b/tests/warn/i16639a.scala @@ -24,13 +24,13 @@ class B3(msg0: String) extends A("msg") // warn /Dotty: unused explicit paramete trait Bing trait Accessors { - private var v1: Int = 0 // warn warn - private var v2: Int = 0 // warn warn, never set - private var v3: Int = 0 + 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 - private[this] var v5 = 0 // warn warn, never set - private[this] var v6 = 0 + private[this] var v5 = 0 // warn, never set + private[this] var v6 = 0 // warn, never got private[this] var v7 = 0 // no warn def bippy(): Int = { @@ -43,13 +43,13 @@ trait Accessors { } class StableAccessors { - private var s1: Int = 0 // warn warn - private var s2: Int = 0 // warn warn, never set - private var s3: Int = 0 + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // warn, never got private var s4: Int = 0 // no warn - private[this] var s5 = 0 // warn warn, never set - private[this] var s6 = 0 // no warn, limitation /Dotty: Why limitation ? + private[this] var s5 = 0 // warn, never set + private[this] var s6 = 0 // warn, never got private[this] var s7 = 0 // no warn def bippy(): Int = { @@ -62,7 +62,7 @@ class StableAccessors { } trait DefaultArgs { - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // no more warn warn since #17061 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // warn // warn def boppy() = bippy(5, 100, 200) } @@ -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 @@ -124,9 +124,9 @@ trait Underwarn { } class OtherNames { - private def x_=(i: Int): Unit = () // no more warn since #17061 - private def x: Int = 42 // warn Dotty triggers unused private member : To investigate - private def y_=(i: Int): Unit = () // // no more warn since #17061 + private def x_=(i: Int): Unit = () // warn + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () // warn private def y: Int = 42 def f = y @@ -145,7 +145,7 @@ trait Forever { val t = Option((17, 42)) for { ns <- t - (i, j) = ns // no warn + (i, j) = ns // warn // warn -Wunused:patvars is in -Wunused:all } yield 42 // val emitted only if needed, hence nothing unused } } @@ -158,14 +158,14 @@ trait CaseyKasem { def f = 42 match { case x if x < 25 => "no warn" case y if toString.nonEmpty => "no warn" + y - case z => "warn" + case z => "warn" // warn patvar } } 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 Some(y @ _) if toString.nonEmpty => "no warn" // warn todo whether to use name @ _ to suppress + case Some(z) => "warn" // warn patvar case None => "no warn" } } @@ -173,7 +173,7 @@ trait CaseyAtTheBat { class `not even using companion privates` object `not even using companion privates` { - private implicit class `for your eyes only`(i: Int) { // no more warn since #17061 + private implicit class `for your eyes only`(i: Int) { // warn def f = i } } @@ -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/i17230.min1.scala b/tests/warn/i17230.min1.scala similarity index 91% rename from tests/pos/i17230.min1.scala rename to tests/warn/i17230.min1.scala index 9ab79433fae2..ff7dae59c289 100644 --- a/tests/pos/i17230.min1.scala +++ b/tests/warn/i17230.min1.scala @@ -1,4 +1,3 @@ -//> using options -Werror trait Foo: type Bar[_] diff --git a/tests/pos/i17230.orig.scala b/tests/warn/i17230.orig.scala similarity index 94% rename from tests/pos/i17230.orig.scala rename to tests/warn/i17230.orig.scala index 279ae41fc32e..4c2f1b8971ad 100644 --- a/tests/pos/i17230.orig.scala +++ b/tests/warn/i17230.orig.scala @@ -1,4 +1,3 @@ -//> using options -Werror import scala.util.* trait Transaction { 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/i17318.scala b/tests/warn/i17318.scala new file mode 100644 index 000000000000..2a534bdb396c --- /dev/null +++ b/tests/warn/i17318.scala @@ -0,0 +1,32 @@ + +//> using options -Wunused:all + +object events { + final val PollOut = 0x002 + transparent inline def POLLIN = 0x001 +} + +def withShort(v: Short): Unit = ??? +def withInt(v: Int): Unit = ??? + +def usage() = + import events.POLLIN // reports unused + def v: Short = POLLIN + println(v) + +def usage2() = + import events.POLLIN // reports unused + withShort(POLLIN) + +def usage3() = + import events.POLLIN // does not report unused + withInt(POLLIN) + +def usage4() = + import events.POLLIN // reports unused + withShort(POLLIN) + +def value = 42 +def withDouble(v: Double): Unit = ??? +def usage5() = withDouble(value) +def usage6() = withShort(events.POLLIN) diff --git a/tests/warn/i17371.scala b/tests/warn/i17371.scala new file mode 100644 index 000000000000..c62c45c4c837 --- /dev/null +++ b/tests/warn/i17371.scala @@ -0,0 +1,39 @@ +//> 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 + +// -Wunused:implicits warns for unused implicit evidence unless it is an empty interface (only universal members). +// scala 2 also offers -Wunused:synthetics for whether to warn for synthetic implicit params. +object ContextBounds: + class C[A: Ordered](a: A): // warn + def f = a + + trait T[A] + + class D[A: T](a: A): // no warn + def f = a diff --git a/tests/warn/i17667.scala b/tests/warn/i17667.scala new file mode 100644 index 000000000000..cc3f6bfc8472 --- /dev/null +++ b/tests/warn/i17667.scala @@ -0,0 +1,10 @@ + +//> using options -Wunused:imports + +object MyImplicits: + extension (a: Int) def print: Unit = println(s"Hello, I am $a") + +import MyImplicits.print //Global import of extension +object Foo: + def printInt(a: Int): Unit = a.print + import MyImplicits.* // warn //Local import of extension diff --git a/tests/warn/i17667b.scala b/tests/warn/i17667b.scala new file mode 100644 index 000000000000..ba8fc7219945 --- /dev/null +++ b/tests/warn/i17667b.scala @@ -0,0 +1,22 @@ + +//> using options -Wunused:all + +import scala.util.Try +import scala.concurrent.* // warn +import scala.collection.Set +class C { + def ss[A](using Set[A]) = println() // warn + private def f = Try(42).get + private def z: Int = // warn + Try(27 + z).get + def g = f + f + def k = + val i = g + g + val j = i + 2 // warn + i + 1 + def c = C() + import scala.util.Try // warn +} +class D { + def d = C().g +} diff --git a/tests/warn/i17753.scala b/tests/warn/i17753.scala new file mode 100644 index 000000000000..66e4fdb8727b --- /dev/null +++ b/tests/warn/i17753.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all + +class PartiallyApplied[A] { + transparent inline def func[B](): Nothing = ??? +} + +def call[A] = new PartiallyApplied[A] + +def good = call[Int].func[String]() // no warn inline proxy +def bad = { call[Int].func[String]() } // no warn inline proxy 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/i18564.scala b/tests/warn/i18564.scala new file mode 100644 index 000000000000..d9ea624c57c1 --- /dev/null +++ b/tests/warn/i18564.scala @@ -0,0 +1,39 @@ + +//> using option -Wunused:imports + +import scala.compiletime.* +import scala.deriving.* + +trait Foo + +trait HasFoo[A]: + /** true if A contains a Foo */ + val hasFoo: Boolean + +// given instances that need to be imported to be in scope +object HasFooInstances: + given defaultHasFoo[A]: HasFoo[A] with + val hasFoo: Boolean = false + given HasFoo[Foo] with + val hasFoo: Boolean = true + +object HasFooDeriving: + + inline private def tupleHasFoo[T <: Tuple]: Boolean = + inline erasedValue[T] match + case _: EmptyTuple => false + case _: (t *: ts) => summonInline[HasFoo[t]].hasFoo || tupleHasFoo[ts] + + inline def deriveHasFoo[T](using p: Mirror.ProductOf[T]): HasFoo[T] = + // falsely reported as unused even though it has influence on this code + import HasFooInstances.given // no warn at inline method + val pHasFoo = tupleHasFoo[p.MirroredElemTypes] + new HasFoo[T]: // warn New anonymous class definition will be duplicated at each inline site + val hasFoo: Boolean = pHasFoo + +/* the import is used upon inline elaboration +object Test: + import HasFooDeriving.* + case class C(x: Foo, y: Int) + def f: HasFoo[C] = deriveHasFoo[C] +*/ diff --git a/tests/warn/i19252.scala b/tests/warn/i19252.scala new file mode 100644 index 000000000000..cab0b86ef236 --- /dev/null +++ b/tests/warn/i19252.scala @@ -0,0 +1,13 @@ +//> using options -Wunused:all +object Deps: + trait D1 + object D2 +end Deps + +object Bug: + import Deps.D1 // no warn + + class Cl(d1: D1): + import Deps.* + def f = (d1, D2) +end Bug diff --git a/tests/warn/i19657-mega.scala b/tests/warn/i19657-mega.scala new file mode 100644 index 000000000000..07411ee73fb1 --- /dev/null +++ b/tests/warn/i19657-mega.scala @@ -0,0 +1,4 @@ +//> using options -Wshadow:type-parameter-shadow -Wunused:all + +class F[X, M[N[X]]]: // warn + private def x[X] = toString // warn // warn diff --git a/tests/warn/i19657.scala b/tests/warn/i19657.scala new file mode 100644 index 000000000000..2caa1c832abe --- /dev/null +++ b/tests/warn/i19657.scala @@ -0,0 +1,117 @@ +//> 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 extra = 3 +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) and does not shadow named import + def m = i // actually picks the higher-precedence import + def f = + import Constantinople.* + class E(e: Int) extends C(i + k): + def g = e + y + k + 1 + E(0).g + def consume = extra // use the wildcard import from Constants + +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/warn/i20520.scala b/tests/warn/i20520.scala new file mode 100644 index 000000000000..e09d16c27af2 --- /dev/null +++ b/tests/warn/i20520.scala @@ -0,0 +1,11 @@ + +//> using options -Wunused:all + +@main def run = + val veryUnusedVariable: Int = value // warn local + +package i20520: + private def veryUnusedMethod(x: Int): Unit = println() // warn param + private val veryUnusedVariableToplevel: Unit = println() // package members are accessible under separate compilation + +def value = 42 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/i20951.scala b/tests/warn/i20951.scala new file mode 100644 index 000000000000..0ca82e1b8d66 --- /dev/null +++ b/tests/warn/i20951.scala @@ -0,0 +1,7 @@ +//> using options -Wunused:all +object Foo { + val dummy = 42 + def f(): Unit = Option(1).map((x: Int) => dummy) // warn + def g(): Unit = Option(1).map((x: Int) => ???) // warn + def main(args: Array[String]): Unit = {} +} diff --git a/tests/warn/i21420.scala b/tests/warn/i21420.scala index 0ee4aa3f28f6..890ea80d4443 100644 --- a/tests/warn/i21420.scala +++ b/tests/warn/i21420.scala @@ -7,7 +7,7 @@ object decisions4s{ object DiagnosticsExample { import decisions4s.HKD - val _ = new HKD {} + case class Input[F[_]]() extends HKD import decisions4s.* - val _ = new DecisionTable {} + val decisionTable: DecisionTable = ??? } diff --git a/tests/warn/i21525.scala b/tests/warn/i21525.scala new file mode 100644 index 000000000000..50ff64fe788a --- /dev/null +++ b/tests/warn/i21525.scala @@ -0,0 +1,20 @@ +//> using options -Wunused:imports + +import scala.reflect.TypeTest + +trait A { + type B + type C <: B + + given instance: TypeTest[B, C] +} + +def f(a: A, b: a.B): Boolean = { + import a.C + b match { + case _: C => + true + case _ => + false + } +} diff --git a/tests/warn/i21809.scala b/tests/warn/i21809.scala new file mode 100644 index 000000000000..91e7a334b13b --- /dev/null +++ b/tests/warn/i21809.scala @@ -0,0 +1,17 @@ +//> using options -Wunused:imports + +package p { + package q { + import q.* // warn so long as we pass typer + class Test { + //override def toString = new C().toString + " for Test" + def d = D() + } + class D + } +} +package q { + class C { + override def toString = "q.C" + } +} diff --git a/tests/warn/i21917.scala b/tests/warn/i21917.scala new file mode 100644 index 000000000000..627cd0fa7d08 --- /dev/null +++ b/tests/warn/i21917.scala @@ -0,0 +1,31 @@ +//> using scala 3.6.2 +//> using options -Wunused:imports +// +//> abusing scala 2.13.15 +//> abusing options -Wunused:imports -Xsource:3 + +import Pet.Owner + +class Dog(owner: Owner) extends Pet(owner) { + import Pet.* // warn although unambiguous + //import Car.* // ambiguous + + def bark(): String = "bite" + + def this(owner: Owner, goodDog: Boolean) = { + this(owner) + if (goodDog) println(s"$owner's dog is a good boy") + } + + val getOwner: Owner = owner +} + +class Pet(val owner: Owner) + +object Pet { + class Owner +} + +object Car { + class Owner +} diff --git a/tests/warn/i3323.scala b/tests/warn/i3323.scala new file mode 100644 index 000000000000..581adf812d04 --- /dev/null +++ b/tests/warn/i3323.scala @@ -0,0 +1,8 @@ + +class Foo { + def foo[A](lss: List[List[A]]): Unit = { + lss match { + case xss: List[List[A]] => // no warn erasure + } + } +} diff --git a/tests/warn/scala2-t11681.scala b/tests/warn/scala2-t11681.scala index ae2187181ceb..5d752777f64c 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 = { @@ -59,10 +59,10 @@ class Revaluing(u: Int) { def f = u } // OK case class CaseyKasem(k: Int) // OK -case class CaseyAtTheBat(k: Int)(s: String) // ok +case class CaseyAtTheBat(k: Int)(s: String) // warn unused s 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 // no warn (that is a patvar) } 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 implicit param even though only marker + def g[A: Context] = answer // no warn bound that is marker only } -class Bound[A: Context] // OK +class Bound[A: Context] // no warn bound that is marker only object Answers { def answer: Int = 42 } diff --git a/tests/warn/unused-can-equal.scala b/tests/warn/unused-can-equal.scala new file mode 100644 index 000000000000..f2513cbe209f --- /dev/null +++ b/tests/warn/unused-can-equal.scala @@ -0,0 +1,16 @@ + +//> using options -Wunused:all + +import scala.language.strictEquality + +class Box[T](x: T) derives CanEqual: + def y = x + +def f[A, B](a: A, b: B)(using CanEqual[A, B]) = a == b // no warn + +def g = + import Box.given // no warn + "42".length + +@main def test() = println: + Box(1) == Box(1L) 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..5ef339c942ac --- /dev/null +++ b/tests/warn/unused-params.scala @@ -0,0 +1,159 @@ +//> 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) // 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 // 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 + def h[A](using Context[A]) = 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 + +object Optional: + extension (opt: Option.type) // no warn for extension of module + @annotation.experimental + inline def fromNullable[T](t: T | Null): Option[T] = Option(t).asInstanceOf[Option[T]] + +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..19ab29ad8d4d --- /dev/null +++ b/tests/warn/unused-privates.scala @@ -0,0 +1,310 @@ +// +//> 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 // warn, never got + private var v4: Int = 0 // no warn + + private var v5 = 0 // warn, never set + private var v6 = 0 // 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 // warn, never got + private var s4: Int = 0 // no warn + + private var s5 = 0 // warn, never set + private var s6 = 0 // warn, never got + private var s7 = 0 // no warn + + def bippy(): Int = { + s3 = 3 + s4 = 4 + s6 = 6 + s7 = 7 + s2 + s4 + s5 + s7 + } +} + +trait DefaultArgs { + // DO warn about default getters for x2 and x3 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // warn // warn + + 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 = () // warn + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () // warn + 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) { // warn + 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 = new 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 = new Object // warn + +@throws(classOf[java.io.ObjectStreamException]) +private def readResolve(): Object = ??? // TODO warn +private def print() = println() // TODO warn +private val printed = false // TODO warn + +package locked: + private[locked] def locker(): Unit = () // TODO 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] } + } + +object `patvar is assignable`: + private var (i, j) = (42, 27) // no warn patvars under -Wunused:privates + println((i, j))