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 eba6d42c2306..011f4b1c3a63 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -33,7 +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(/*LintPreparation(),*/ CheckUnused.PostTyper()/*, CheckShadowing()*/) :: // Check for unused, shadowed 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 @@ -51,7 +51,7 @@ class Compiler { 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(/*LintPreparation(),*/ CheckUnused.PostInlining()) :: // Check for unused elements + 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/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 16976314ce8c..9005e75856cb 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3290,16 +3290,17 @@ 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) = "" + override def actions(using Context) = this.actions } object UnusedSymbol: - def imports(using Context): UnusedSymbol = UnusedSymbol(i"unused import") + 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") diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 1cb5a2cfd29d..e21fce1e1e58 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,7 +1,5 @@ package dotty.tools.dotc.transform -import scala.annotation.* - import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar} import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.ast.untpd, untpd.ImportSelector @@ -9,7 +7,7 @@ import dotty.tools.dotc.config.ScalaSettings import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, termName} -import dotty.tools.dotc.core.NameOps.isReplWrapperName +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} @@ -21,6 +19,8 @@ import dotty.tools.dotc.typer.ImportInfo import dotty.tools.dotc.util.{Property, SrcPos} import dotty.tools.dotc.util.chaining.* +import java.util.IdentityHashMap + import scala.collection.mutable, mutable.{ArrayBuilder, ListBuffer, Stack} import CheckUnused.* @@ -38,40 +38,11 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha override def isRunnable(using Context): Boolean = super.isRunnable && ctx.settings.WunusedHas.any && !ctx.isJava override def prepareForUnit(tree: Tree)(using Context): Context = - /* - val checkAtts = new TreeTraverser: - def traverse(tree: Tree)(using Context) = tree match - case tree => - if tree.allAttachments.nonEmpty then - println(s"${tree} ${tree.allAttachments.nonEmpty}") - traverseChildren(tree) - checkAtts.traverse(tree) - */ 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 = - /* - val checkAnnots = new TreeTraverser: - def traverse(tree: Tree)(using Context) = tree match - case tree => - if tree.symbol.exists then - println(s"${tree.symbol} ${tree.symbol.id} -> ${tree.symbol.annotations}") - traverseChildren(tree) - checkAnnots.traverse(tree) - */ - /* - locally: - import dotty.tools.dotc.reporting.{Diagnostic, MessageRendering} - val ws = warnings - if ws.exists(!_._1.msg.contains("unused import")) then - val render = new MessageRendering {} - //println(s"at $phaseMode ${ws.length} warnings\n${tree.show}") - println(s"at $phaseMode ${ws.length} warnings") - if phaseMode != PhaseMode.Report then - ws.foreach((msg, pos) => println(render.messageAndPos(Diagnostic.Warning(msg, pos.sourcePos)))) - */ if phaseMode == PhaseMode.Report then reportUnused() tree.removeAttachment(refInfosKey) @@ -85,9 +56,14 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha 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) - resolveUsage(tree.symbol, name, tree.qualifier.tpe) + if tree.qualifier.span.isSynthetic || name.exists(_ != tree.symbol.name) then + if !ignoreTree(tree) then + resolveUsage(tree.symbol, name, tree.qualifier.tpe) + else + refUsage(tree.symbol) tree override def prepareForCaseDef(tree: CaseDef)(using Context): Context = @@ -104,37 +80,39 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha case _ => ctx + 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 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 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 tpt if !tree.isInferred && tpt.typeSymbol.exists => resolveUsage(tpt.typeSymbol, tpt.typeSymbol.name, NoPrefix) case _ => tree + override def transformInlined(tree: Inlined)(using Context): tree.type = + if !tree.call.isEmpty then + transformAllDeep(tree.call) + tree + override def prepareForBind(tree: Bind)(using Context): Context = refInfos.register(tree) ctx override def prepareForValDef(tree: ValDef)(using Context): Context = - /* - // selftype of object is not a usage - val moduleSelfRef = ctx.owner.is(Module) && ctx.owner == tree.symbol.companionModule.moduleClass - if !moduleSelfRef then - - if !tree.symbol.is(Module) then // do not register the ValDef generated for `object` - if ctx.owner.is(Module) then //&& tree.symbol.is(SelfName) then - println(s"AHA selfie $tree") - tree.tpt match - case SingletonTypeTree(ref) => ctx.owner == ref.symbol.companionModule.moduleClass - case _ => - */ refInfos.register(tree) ctx override def transformValDef(tree: ValDef)(using Context): tree.type = @@ -152,18 +130,31 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha tree override def prepareForDefDef(tree: DefDef)(using Context): Context = - val rhs = tree.rhs - val trivial = - tree.symbol.is(Deferred) - || rhs.symbol == defn.Predef_undefined + def isUnconsuming(rhs: Tree): 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 - def nontrivial = tree.symbol.isAnonymousFunction - if trivial && !nontrivial then refInfos.skip.addOne(tree.symbol) + //|| 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 This(_) => true + case Ident(_) => rhs.symbol.is(ParamAccessor) + case Typed(rhs, _) => isUnconsuming(rhs) + case _ => false + 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) refInfos.register(tree) ctx override def transformDefDef(tree: DefDef)(using Context): tree.type = @@ -233,28 +224,30 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha transformAllDeep(body) typeargs.foreach(transformAllDeep) args.foreach(transformAllDeep) - case _: InferredTypeTree => + 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}") + println(s"OTHER TYPE ${tree.getClass} ${tree.show}") case _ => - //println(s"OTHER ${tree.getClass} ${tree.show}") + println(s"OTHER ${tree.getClass} ${tree.show}") tree private def traverseAnnotations(sym: Symbol)(using Context): Unit = for annot <- sym.denot.annotations do transformAllDeep(annot.tree) - /** Look up a reference in contexts to determine whether it was introduced by a definition or import. - * - * The "usage" is recorded in the corresponding context. The reference is recorded here, - * since it's necessary to perform the lookup only once. - * - * The first matching context must be correct; we are not rechecking name resolution. - * If there is no matching context, a root context must have been used for name resolution. + /** 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. */ - def resolveUsage(sym: Symbol, name: Name, prefix: Type = NoPrefix)(using Context): Unit = + def resolveUsage(sym: Symbol, name: Name, prefix: Type)(using Context): Unit = def matchingSelector(info: ImportInfo): ImportSelector | Null = - val qtpe = info.qualifier.tpe.nn + val qtpe = info.site //info.qualifier.tpe.nn def loop(sels: List[ImportSelector]): ImportSelector | Null = sels match case sel :: sels if sel.isWildcard => @@ -284,13 +277,14 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha && ctxsym.thisType.member(sym.name).hasAltWith(d => d.containsSym(sym) && !name.exists(_ != d.name)) def addCached(where: Context): Unit = - 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 _ => + 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 _ => if !sym.exists then return @@ -304,10 +298,12 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha 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 - val ctxs = ctx.outersIterator + 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 @@ -316,18 +312,25 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha else if isLocal then if cur.owner eq sym.owner then done = true // only checking for enclosing else - val cached = + cached = cur.property(resolvedKey) match - case Some(res) => res.seen(sym).exists((n, p) => n == name && p =:= prefix) + 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 - println(s"CACHED $sym") candidate = cur done = true else if cur.isImportContext then val sel = matchingSelector(cur.importInfo.nn) if sel != null then - if sel.isWildcard 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 @@ -341,6 +344,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if sym.srcPos.sourcePos.source == ctx.source then precedence = 1 candidate = cur + importer = null done = true else if precedence > 4 then precedence = 4 @@ -349,9 +353,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha if !foundEnclosing then refInfos.refs.addOne(sym) if phaseMode.eq(PhaseMode.Aggregate) && candidate != NoContext && candidate.isImportContext && importer != null - then refInfos.sels.addOne(importer) - //addCached(candidate) + 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: @@ -380,6 +391,9 @@ object CheckUnused: /** Suppress warning in a tree, such as a patvar absolved of unused warning by special naming convention. */ val NoWarn = Property.StickyKey[Unit] + /** Ignore reference. */ + val Ignore = Property.StickyKey[Unit] + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper") class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") @@ -389,17 +403,17 @@ object CheckUnused: 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 - val imps = mutable.Set.empty[Import] // imports - val sels = mutable.Set.empty[ImportSelector] // matched selectors + 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 = tree match case imp: Import => if languageImport(imp.expr).isEmpty && !imp.isGeneratedByEnum - && !imp.isTransparentInline + //&& !imp.isTransparentInline then - imps.addOne(imp) + 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)) @@ -419,6 +433,8 @@ object CheckUnused: // 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 = warnings.foreach(report.warning(_, _)) @@ -437,14 +453,17 @@ object CheckUnused: && !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 warnings.addOne((UnusedSymbol.privateMembers, pos)) else if sym.is(Param, butNot = Given | Implicit) then val m = sym.owner def forgiven(sym: Symbol) = val dd = defn - sym.owner.isClass && sym.owner.thisType.baseClasses.contains(defn.AnnotationClass) - || true + sym.owner.isDeprecated + || sym.owner.hasAnnotation(defn.UnusedAnnot) // param of unused method + || sym.info.isSingleton + || sym.owner.isConstructor && sym.owner.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.) @@ -469,14 +488,6 @@ object CheckUnused: case _ => false }) && !sym.name.isInstanceOf[DerivedName] - /* - && { - sym.name match - //case n: SimpleName => !n.contains('$') - case n: DerivedName => false - case _ => true - } - */ && !ctx.platform.isMainMethod(m) then warnings.addOne((UnusedSymbol.explicitParams, pos)) @@ -485,7 +496,7 @@ object CheckUnused: && !forgiven(sym) then checkExplicit() - else if sym.is(Param) then + else if sym.is(Param) then // Given | Implicit val m = sym.owner def forgiven(sym: Symbol) = val dd = defn @@ -496,7 +507,9 @@ object CheckUnused: 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(sym) @@ -510,7 +523,9 @@ object CheckUnused: else warnings.addOne((UnusedSymbol.implicitParams, pos)) else if sym.isLocalToBlock then - if ctx.settings.WunusedHas.locals then + if ctx.settings.WunusedHas.locals + && !sym.isCanEqual + then warnings.addOne((UnusedSymbol.localDefs, pos)) if ctx.settings.WunusedHas.patvars then @@ -519,23 +534,27 @@ object CheckUnused: if pos.span.isSynthetic then pos else pos.sourcePos.withSpan(pos.span.toSynthetic) // patvars in for comprehensions have the pos of where the name was introduced val byPos = infos.pats.groupMap(uniformPos(_, _))((sym, pos) => sym) - //println(s"refs ${infos.refs}") - //println(s"byPos $byPos") for (pos, syms) <- byPos if !syms.exists(_.hasAnnotation(defn.UnusedAnnot)) do if !syms.exists(infos.refs(_)) && !syms.exists(v => !v.isLocal && !v.is(Private)) then - //println(s"PAT SYMS at $pos are $syms ${syms.map(_.name.decode)}") warnings.addOne((UnusedSymbol.patVars, pos)) + import scala.jdk.CollectionConverters.given + val actionable = false && ctx.settings.rewrite.value.nonEmpty if (ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn) && !infos.isRepl then - for imp <- infos.imps; sel <- imp.selectors if !sel.isImportExclusion && !infos.sels(sel) && !imp.isLoose(sel) do - warnings.addOne((UnusedSymbol.imports, sel.srcPos)) + for imp <- infos.imps.keySet.nn.asScala do + if actionable then + ??? + else + for sel <- imp.selectors do + if !sel.isImportExclusion && !infos.sels.containsKey(sel) && !imp.isLoose(sel) then + warnings.addOne((UnusedSymbol.imports(actions = Nil), sel.srcPos)) warnings.result().sorta(_._2.span.point) end warnings // Specific exclusions def ignoreTree(tree: Tree): Boolean = - tree.hasAttachment(ForArtifact) + tree.hasAttachment(ForArtifact) || tree.hasAttachment(Ignore) // NoWarn Binds if the name matches a "canonical" name, e.g. case element name val nowarner = new TreeTraverser: @@ -546,27 +565,33 @@ object CheckUnused: case _ => 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 - () - /* - 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, targs) if tycon.typeSymbol == defn.TypeTestClass => - val target = targs(1).dealias.typeSymbol - println(s"AT of $target cf $args") + 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 val Quotes_reflect: Symbol = defn.QuotesClass.requiredClass("reflectModule") - if unapplied.owner == Quotes_reflect then + 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) @@ -608,7 +633,9 @@ object CheckUnused: extension (sym: Symbol) def isSerializationSupport(using Context): Boolean = sym.is(Method) && serializationNames(sym.name.toTermName) && sym.owner.isClass - && sym.owner.asClass.classDenot.parentSyms.exists(_.info.dealias.typeSymbol == defn.JavaSerializableClass) + && sym.owner.derivesFrom(defn.JavaSerializableClass) + def isCanEqual(using Context): Boolean = + sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) extension (sel: ImportSelector) def boundTpe: Type = sel.bound match @@ -626,21 +653,27 @@ object CheckUnused: def isGeneratedByEnum(using Context): Boolean = imp.symbol.exists && imp.symbol.owner.is(Enum, butNot = Case) - /** Checks if import selects a def that is transparent and inline. */ + /** Checks if import selects a def that is transparent and inline. def isTransparentInline(using Context): Boolean = val qual = imp.expr imp.selectors.exists: sel => val importedMembers = qual.tpe.member(sel.name).alternatives importedMembers.exists(_.symbol.isAllOf(Transparent | Inline)) + */ /** 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. */ 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) ) // incredibly, there is no "sort in place" for array diff --git a/compiler/src/dotty/tools/dotc/transform/LintPreparation.scala b/compiler/src/dotty/tools/dotc/transform/LintPreparation.scala deleted file mode 100644 index a3d69952dd4a..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/LintPreparation.scala +++ /dev/null @@ -1,170 +0,0 @@ - -package dotty.tools.dotc.transform - -/* -import scala.annotation.* - -import dotty.tools.uncheckedNN -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, toMessage} -import dotty.tools.dotc.core.Denotations.SingleDenotation -import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.StdNames.nme -import dotty.tools.dotc.core.Types.{AnnotatedType, ClassInfo, ConstantType, NamedType, NoPrefix, NoType, TermRef, Type, TypeProxy, TypeTraverser} -import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.{Name, TermName, termName} -import dotty.tools.dotc.core.NameOps.isReplWrapperName -import dotty.tools.dotc.core.NameKinds.{ContextFunctionParamName, WildcardParamName} -import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol, defn, isDeprecated} -import dotty.tools.dotc.report -import dotty.tools.dotc.reporting.{Message, UnusedSymbol as UnusedSymbolMessage} -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.util.{Property, SrcPos} -import dotty.tools.dotc.util.Spans.Span -import scala.util.chaining.given -*/ - -import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.core.Contexts.{Context, ctx} -import dotty.tools.dotc.core.Names.Name -import dotty.tools.dotc.core.Scopes, Scopes.MutableScope -import dotty.tools.dotc.core.Symbols.{Symbol, defn} -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.util.{Property} - -//import scala.compiletime.uninitialized - -/** A compiler phase that prepares contexts for linting. - */ -class LintPreparation extends MiniPhase: - import LintPreparation.* - - override def phaseName = "lintPreparation" - - override def description = "prepare contexts for linting" - - override def isEnabled(using Context): Boolean = ctx.settings.WunusedHas.any - - override def isRunnable(using Context): Boolean = super.isRunnable && ctx.settings.WunusedHas.any && !ctx.isJava - - override def prepareForUnit(tree: Tree)(using Context): Context = - ctx.fresh.setProperty(ctxKey, LintContexts()) - - override def prepareForValDef(tree: ValDef)(using Context): Context = - ctx - - override def prepareForDefDef(tree: DefDef)(using Context): Context = - assert(tree.symbol.owner eq ctx.owner, s"tree ${tree.symbol}, ctx ${ctx.owner}") - pushLintContext(LintContext(tree.symbol)) - ctx - override def transformDefDef(tree: DefDef)(using Context): tree.type = - popLintContext() match - case LintNamed(sym) => assert(sym eq tree.symbol) - case x => throw MatchError(x) - tree - - override def prepareForTemplate(tree: Template)(using Context): Context = - assert(tree.symbol.owner eq ctx.owner, s"tree ${tree.symbol}, ctx ${ctx.owner}") - pushLintContext(LintContext(ctx.owner)) - ctx - override def transformTemplate(tree: Template)(using Context): Tree = - popLintContext() match - case LintNamed(sym) => assert(sym eq tree.symbol.owner) - case x => throw MatchError(x) - tree - - override def prepareForPackageDef(tree: PackageDef)(using Context): Context = - if tree.symbol.isEmptyPackage then assert(ctx.owner eq defn.RootClass) - else assert(tree.symbol eq ctx.owner, s"tree ${tree.symbol}, ctx ${ctx.owner}") - pushLintContext(LintContext(tree.symbol)) - ctx - override def transformPackageDef(tree: PackageDef)(using Context): Tree = - popLintContext() match - case LintNamed(sym) => assert(sym eq tree.symbol) - case x => throw MatchError(x) - tree - - override def prepareForStats(trees: List[Tree])(using Context): Context = - val lctx = LintLocal() - pushLintContext(lctx) - trees.foreach: - //case tree: MemberDef => lctx.scope.enter(tree.name, tree.symbol) - case tree: MemberDef => - println(s"STATLOC ${tree.name} ${tree.symbol}") - lctx.scope.enter(tree.name, tree.symbol) - case _ => - ctx - override def transformStats(trees: List[Tree])(using Context): List[Tree] = - def popImports(): Unit = - popLintContext() match - case LintLocal() => - case LintImport(_) => popImports() - case x => throw MatchError(x) - popImports() - trees - - override def prepareForOther(tree: Tree)(using Context): Context = - tree match - case imp: Import => - pushLintContext(LintContext(imp)) - case _ => - ctx - -object LintPreparation: - import collection.mutable, mutable.Stack - - private val ctxKey = Property.StickyKey[LintContexts] - - /** The context for linting. - */ - inline def lintContext(using Context): LintContext = - ctx.property(ctxKey) match - case Some(ctxs) => ctxs.cur - case None => throw new RuntimeException(s"no lint context") - - /** Iterate over all contexts for linting, inner to outer. - */ - inline def lintContexts(using Context): Iterator[LintContext] = - ctx.property(ctxKey) match - case Some(ctxs) => ctxs.ctxs.iterator - case None => Iterator.empty[LintContext] - - def pushLintContext(lintContext: LintContext)(using Context): Unit = - ctx.property(ctxKey) match - case Some(ctxs) => ctxs.push(lintContext) - case None => throw new RuntimeException(s"no lint context") - - def popLintContext()(using Context): LintContext = - ctx.property(ctxKey) match - case Some(ctxs) => ctxs.pop() - case None => throw new RuntimeException(s"no lint context") - - sealed abstract class LintContext: - def introduces(name: Name): Boolean - - object LintContext: - def apply(owner: Symbol)(using Context): LintContext = LintNamed(owner) - def apply(tree: Import)(using Context): LintContext = LintImport(tree) - def apply()(using Context): LintContext = LintLocal() - - case class LintNamed(owner: Symbol) extends LintContext: - override def introduces(name: Name) = false - - case class LintImport(tree: Import) extends LintContext: - override def introduces(name: Name) = false - - case class LintLocal() extends LintContext: - override def introduces(name: Name) = false - val scope: MutableScope = Scopes.newScope(0) - - class LintContexts: - val ctxs = Stack.empty[LintContext] - def cur: LintContext = ctxs.top - def push(ctx: LintContext): ctx.type = { ctxs.push(ctx); ctx } - def pop(): LintContext = ctxs.pop() - -end LintPreparation 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/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 6da7f940dc47..82d9dd61b9e2 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 afc7ce792a1f..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 @@ -184,11 +184,11 @@ inline def summonAll[T <: Tuple]: T = 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/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 394e0895c4ee..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( @@ -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/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/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/tuple-exaustivity.scala b/tests/pos/tuple-exaustivity.scala index a27267fc89e5..26090024f9cb 100644 --- a/tests/pos/tuple-exaustivity.scala +++ b/tests/pos/tuple-exaustivity.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -deprecation -feature +//> using options -Werror -deprecation -feature def test(t: Tuple) = t match 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..e1804e594d42 --- /dev/null +++ b/tests/rewrites/unused.check @@ -0,0 +1,7 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + import java.lang.Thread + class C extends Runnable { def run() = () } diff --git a/tests/rewrites/unused.scala b/tests/rewrites/unused.scala new file mode 100644 index 000000000000..e1804e594d42 --- /dev/null +++ b/tests/rewrites/unused.scala @@ -0,0 +1,7 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + import java.lang.Thread + class C extends Runnable { def run() = () } diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index 0316efd2a06d..8413023f0492 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -275,3 +275,44 @@ package foo.test.typeapply.hklamdba.i16680: def f[F[_]]: String = "hello" 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 diff --git a/tests/warn/i15503c.scala b/tests/warn/i15503c.scala index b0f7ebae807a..86b972487e17 100644 --- a/tests/warn/i15503c.scala +++ b/tests/warn/i15503c.scala @@ -61,3 +61,12 @@ package test.foo.i16682: } 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 9e8c3f15e9df..9cbeef1902c3 100644 --- a/tests/warn/i15503d.scala +++ b/tests/warn/i15503d.scala @@ -80,3 +80,15 @@ class C(c0: Option[Int], k0: K): 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) diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index a0f45d4a6cd3..7535d3d81593 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -1,5 +1,7 @@ //> using options -Wunused:explicits +import annotation.* + object Foo { /* This goes around the "trivial method" detection */ val default_val = 1 @@ -36,7 +38,7 @@ package foo.test.lambda.param: 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 @@ -50,8 +52,10 @@ 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 @@ -69,3 +73,19 @@ package foo.test.i16865: object Ex2 extends Bar: 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") diff --git a/tests/warn/i15503i.scala b/tests/warn/i15503i.scala index aa0bcf529464..4c90ee3b1119 100644 --- a/tests/warn/i15503i.scala +++ b/tests/warn/i15503i.scala @@ -31,11 +31,10 @@ 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 // warn + case x: 2 => x // OK + case _ => 1 // OK } /* ---- CHECK scala.annotation.unused ---- */ 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/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/i21917.scala b/tests/warn/i21917.scala index 12d7172856bd..627cd0fa7d08 100644 --- a/tests/warn/i21917.scala +++ b/tests/warn/i21917.scala @@ -1,21 +1,31 @@ -//> using options -Wunused:all +//> 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.* + import Pet.* // warn although unambiguous + //import Car.* // ambiguous def bark(): String = "bite" - def this(owner: Owner, goodDog: Boolean) = + def this(owner: Owner, goodDog: Boolean) = { this(owner) - if goodDog then println(s"$owner's dog is a good boy") + if (goodDog) println(s"$owner's dog is a good boy") + } - //val getOwner: Owner = owner + val getOwner: Owner = owner } -trait Pet(owner: Owner) +class Pet(val owner: Owner) object Pet { class Owner } + +object Car { + class Owner +} 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-params.scala b/tests/warn/unused-params.scala index e157764b62dc..0b240175d1b5 100644 --- a/tests/warn/unused-params.scala +++ b/tests/warn/unused-params.scala @@ -60,7 +60,7 @@ class Revaluing(u: Int) { def f = u } // no warn case class CaseyKasem(k: Int) // no warn -case class CaseyAtTheBat(k: Int)(s: String) // NO warn +case class CaseyAtTheBat(k: Int)(s: String) // warn trait Ignorance { def f(readResolve: Int) = answer // warn @@ -94,14 +94,15 @@ trait Anonymous { def f2: Int => Int = _ + 1 // no warn placeholder syntax (a fresh name and synthetic parameter) - def g = for (i <- List(1)) yield answer // warn // TODO no warn patvar elaborated as map.(i => 42) + 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 g[A: Context] = answer // no warn + def h[A](using Context[A]) = answer // warn } -class Bound[A: Context] // warn +class Bound[A: Context] // no warn object Answers { def answer: Int = 42 } @@ -148,6 +149,11 @@ class TryStart(start: String) { 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 index 963fa3093e48..ae6894044560 100644 --- a/tests/warn/unused-privates.scala +++ b/tests/warn/unused-privates.scala @@ -25,11 +25,11 @@ class B3(msg0: String) extends A("msg") trait Accessors { private var v1: Int = 0 // warn private var v2: Int = 0 // warn, never set - private var v3: Int = 0 // NO warn, never got + private var v3: Int = 0 // warn, never got private var v4: Int = 0 // no warn private var v5 = 0 // warn, never set - private var v6 = 0 // NO warn, never got + private var v6 = 0 // warn, never got private var v7 = 0 // no warn def bippy(): Int = { @@ -44,11 +44,11 @@ trait Accessors { class StableAccessors { private var s1: Int = 0 // warn private var s2: Int = 0 // warn, never set - private var s3: Int = 0 // NO warn, never got + private var s3: Int = 0 // warn, never got private var s4: Int = 0 // no warn private var s5 = 0 // warn, never set - private var s6 = 0 // no warn, limitation + private var s6 = 0 // warn, never got private var s7 = 0 // no warn def bippy(): Int = { @@ -61,8 +61,8 @@ class StableAccessors { } trait DefaultArgs { - // NO warn about default getters for x2 and x3 - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 + // 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) } @@ -128,9 +128,9 @@ trait Underwarn { } class OtherNames { - private def x_=(i: Int): Unit = () + private def x_=(i: Int): Unit = () // warn private def x: Int = 42 // warn - private def y_=(i: Int): Unit = () + private def y_=(i: Int): Unit = () // warn private def y: Int = 42 def f = y @@ -209,7 +209,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 warn deprecated feature + private implicit class `for your eyes only`(i: Int) { // warn no warn deprecated feature TODO def f = i } } @@ -287,12 +287,12 @@ class `absolve ONLY serial framework`: private def readResolve(): Object = ??? // warn @throws(classOf[java.io.ObjectStreamException]) -private def readResolve(): Object = ??? // warn -private def print() = println() // warn -private val printed = false // warn +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 = () // warn as we cannot distinguish unqualified private at top level + 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