Skip to content

Nowarn receiver of extension taking params #23351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
Expand Up @@ -530,12 +530,15 @@ object CheckUnused:
// 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.
// Avoid warning for case class elements because they are aliased via unapply (i.e. may be extracted).
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.isAllOf(PrivateParamAccessor, butNot = CaseAccessor)
&& !infos.refs(alias.symbol)
&& !usedByDefaultGetter(sym, m)
then
if aliasSym.is(Local) then
if ctx.settings.WunusedHas.explicits then
warnAt(pos)(UnusedSymbol.explicitParams(aliasSym))
Expand All @@ -544,13 +547,14 @@ object CheckUnused:
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
&& !(sym.owner.is(ExtensionMethod) &&
m.paramSymss.dropWhile(_.exists(_.isTypeParam)).match
case (h :: Nil) :: _ => h == sym // param is the extended receiver
case _ => false
})
)
&& !sym.name.isInstanceOf[DerivedName]
&& !ctx.platform.isMainMethod(m)
&& !usedByDefaultGetter(sym, m)
then
warnAt(pos)(UnusedSymbol.explicitParams(sym))
end checkExplicit
Expand All @@ -562,6 +566,16 @@ object CheckUnused:
checkExplicit()
end checkParam

// does the param have an alias in a default arg method that is used?
def usedByDefaultGetter(param: Symbol, meth: Symbol): Boolean =
val cls = if meth.isConstructor then meth.enclosingClass.companionModule else meth.enclosingClass
val MethName = meth.name
cls.info.decls.exists: d =>
d.name match
case DefaultGetterName(MethName, _) =>
d.paramSymss.exists(_.exists(p => p.name == param.name && infos.refs(p)))
case _ => false

def checkImplicit(sym: Symbol, pos: SrcPos) =
val m = sym.owner
def allowed =
Expand Down Expand Up @@ -591,9 +605,12 @@ object CheckUnused:
val checking =
aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor)
|| aliasSym.isAllOf(Protected | ParamAccessor, butNot = CaseAccessor) && m.owner.is(Given)
if checking && !infos.refs(alias.symbol) then
if checking
&& !infos.refs(alias.symbol)
&& !usedByDefaultGetter(sym, m)
then
warnAt(pos)(UnusedSymbol.implicitParams(aliasSym))
else
else if !usedByDefaultGetter(sym, m) then
warnAt(pos)(UnusedSymbol.implicitParams(sym))

def checkLocal(sym: Symbol, pos: SrcPos) =
Expand Down
2 changes: 1 addition & 1 deletion tests/warn/i15503e.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ object UnwrapTyped:
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
def f(i: Int)(j: Int = i * 2) = j // ~warn~ I guess (see tests/warn/i23349.scala)
30 changes: 30 additions & 0 deletions tests/warn/i23349.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//> using options -Wunused:explicits,implicits

// An external class that doesn't get its own `copy` method.
class Foo(val a: String, val b: Int)

//
// Example 1: add `copy` method via an extension method.
//
extension (self: Foo)
def copy(a: String = self.a, b: Int = self.b): Foo = Foo(a, b) // nowarn

//
// Example 2: implement `copyFoo` with parameter groups.
//
def copyFoo(foo: Foo)(a: String = foo.a, b: Int = foo.b): Foo = Foo(a, b)

class C:
def copyFoo(foo: Foo, bar: String)(a: String = foo.a, b: Int = foo.b)(c: String = bar): Foo = Foo(a, b) // warn c
def copyUsing(using foo: Foo, bar: String)(a: String = foo.a, b: Int = foo.b)(c: String = bar): Foo = // warn c
Foo(a, b)

class K(k: Int)(s: String = "*"*k):
override val toString = s

class KU(using k: Int)(s: String = "*"*k):
override val toString = s

class KK(s: String):
def this(k: Int)(s: String = "*"*k) = this(s)
override val toString = s
Loading