From 302168e0d4baf1579e2e0de6efb8129201ab76b2 Mon Sep 17 00:00:00 2001 From: som-snytt Date: Tue, 3 Jun 2025 05:06:00 -0700 Subject: [PATCH] Dealias for unused param check (#23256) Fixes #23250 The check for "unused implicit parameter" considers the type of the parameter, but it should dealias first. In addition, take an abstract type as not warnable, unless it has an upper bound that is warnable (has non-universal members). Note that `isSingleton` dealiases. There was an ancient (OG) typo of `&` for `&&` which has gone unfixed despite other edits. The right operand was always just an instanceof. Not sure if the previous idiom saves an opcode, but now it looks like other code nearby. [Cherry-picked 2d1865510867aba338813e0ac265f6a8fa70ed35] --- .../dotty/tools/dotc/core/SymDenotations.scala | 2 +- .../tools/dotc/transform/CheckUnused.scala | 6 +++--- tests/warn/i17314.scala | 4 ++-- tests/warn/i23250.scala | 17 +++++++++++++++++ tests/warn/i23250b.scala | 18 ++++++++++++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 tests/warn/i23250.scala create mode 100644 tests/warn/i23250b.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 00530c3a6936..889e3d12a98c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -684,7 +684,7 @@ object SymDenotations { isAbstractOrAliasType && !isAbstractOrParamType /** Is this symbol an abstract or alias type? */ - final def isAbstractOrAliasType: Boolean = isType & !isClass + final def isAbstractOrAliasType: Boolean = isType && !isClass /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(using Context): Boolean = this.isOneOf(DeferredOrTypeParam) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 41ecd9e6c299..7c08ed7415b8 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -613,14 +613,14 @@ object CheckUnused: || m.hasAnnotation(dd.UnusedAnnot) // param of unused method || sym.owner.name.isContextFunction // a ubiquitous parameter || sym.isCanEqual - || sym.info.typeSymbol.match // more ubiquity + || sym.info.dealias.typeSymbol.match // more ubiquity case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true case tps => tps.isMarkerTrait // no members to use; was only if sym.name.is(ContextBoundParamName) || // but consider NotGiven tps.hasAnnotation(dd.LanguageFeatureMetaAnnot) || sym.info.isSingleton // DSL friendly - || sym.info.isInstanceOf[RefinedType] // can't be expressed as a context bound + || sym.info.dealias.isInstanceOf[RefinedType] // can't be expressed as a context bound if ctx.settings.WunusedHas.implicits && !infos.skip(m) && !m.isEffectivelyOverride @@ -943,7 +943,7 @@ object CheckUnused: def isCanEqual: Boolean = sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) def isMarkerTrait: Boolean = - sym.isClass && sym.info.allMembers.forall: d => + sym.info.hiBound.allMembers.forall: d => val m = d.symbol !m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass) def isEffectivelyPrivate: Boolean = diff --git a/tests/warn/i17314.scala b/tests/warn/i17314.scala index cff90d843c38..3b5a486a6785 100644 --- a/tests/warn/i17314.scala +++ b/tests/warn/i17314.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all -deprecation -feature +//> using options -Wunused:all -deprecation -feature -Werror import java.net.URI @@ -10,7 +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] = // warn // warn + inline final def derived[A](using conf: Configuration)(using inline mirror: Mirror.Of[A]): ConfiguredCodec[A] = class InlinedConfiguredCodec extends ConfiguredCodec[A]: val codec = summonInline[Codec[URI]] // simplification new InlinedConfiguredCodec diff --git a/tests/warn/i23250.scala b/tests/warn/i23250.scala new file mode 100644 index 000000000000..ac9183ed1fd1 --- /dev/null +++ b/tests/warn/i23250.scala @@ -0,0 +1,17 @@ +//> using options -Wunused:all -Werror + +trait MonadError[F[_], E] +type MonadThrow[F[_]] = MonadError[F, Throwable] + +trait MetaStreams[F[_]]: + def use[A]: F[A] = ??? +trait WriteResult + +trait MetaStreamsSyntax: + extension [F[_]](ms: MetaStreams[F])(using MonadThrow[F]) + def setMaxAge(): F[WriteResult] = + summon[MonadThrow[F]] + ms.use[WriteResult] + + def setTruncateBefore(): F[WriteResult] = + ms.use[WriteResult] diff --git a/tests/warn/i23250b.scala b/tests/warn/i23250b.scala new file mode 100644 index 000000000000..f9e18559fb46 --- /dev/null +++ b/tests/warn/i23250b.scala @@ -0,0 +1,18 @@ +//> using options -Wunused:all + +trait Memberly: + def member: Int + +object Members: + type MemberType <: Memberly + type Empty + +object Test: + import Members.* + + type MT = MemberType + def membered(using MT) = println() // warn abstract type offers member in upper bound + def remembered(using mt: MT) = mt.member + + type Ignore = Empty + def emptily(using Ignore) = println()