Skip to content

Commit a110500

Browse files
authored
Nowarn receiver of extension taking params (#23351)
Fixes #23349 Extensions are regular methods, but the "receiver" parameter is exempt from the unused check. By error, previously `x.m` was exempt but not `x.f(y)`. (The reason for the exemption may be that the method expressed as a member of the receiver type may make no reference to `this`, without warning.) Check for parameters used only in default arg expressions. A parameter may be aliased in a default arg getter method, so a usage of that getter param counts as a usage of the method param (or class param). Defaults of class params are found in the class companion.
2 parents a1b5446 + aef0a25 commit a110500

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -530,12 +530,15 @@ object CheckUnused:
530530
// A class param is unused if its param accessor is unused.
531531
// (The class param is not assigned to a field until constructors.)
532532
// A local param accessor warns as a param; a private accessor as a private member.
533-
// Avoid warning for case class elements because they are aliased via unapply.
533+
// Avoid warning for case class elements because they are aliased via unapply (i.e. may be extracted).
534534
if m.isPrimaryConstructor then
535535
val alias = m.owner.info.member(sym.name)
536536
if alias.exists then
537537
val aliasSym = alias.symbol
538-
if aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor) && !infos.refs(alias.symbol) then
538+
if aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor)
539+
&& !infos.refs(alias.symbol)
540+
&& !usedByDefaultGetter(sym, m)
541+
then
539542
if aliasSym.is(Local) then
540543
if ctx.settings.WunusedHas.explicits then
541544
warnAt(pos)(UnusedSymbol.explicitParams(aliasSym))
@@ -544,13 +547,14 @@ object CheckUnused:
544547
warnAt(pos)(UnusedSymbol.privateMembers)
545548
else if ctx.settings.WunusedHas.explicits
546549
&& !sym.is(Synthetic) // param to setter is unused bc there is no field yet
547-
&& !(sym.owner.is(ExtensionMethod) && {
548-
m.paramSymss.dropWhile(_.exists(_.isTypeParam)) match
549-
case (h :: Nil) :: Nil => h == sym // param is the extended receiver
550+
&& !(sym.owner.is(ExtensionMethod) &&
551+
m.paramSymss.dropWhile(_.exists(_.isTypeParam)).match
552+
case (h :: Nil) :: _ => h == sym // param is the extended receiver
550553
case _ => false
551-
})
554+
)
552555
&& !sym.name.isInstanceOf[DerivedName]
553556
&& !ctx.platform.isMainMethod(m)
557+
&& !usedByDefaultGetter(sym, m)
554558
then
555559
warnAt(pos)(UnusedSymbol.explicitParams(sym))
556560
end checkExplicit
@@ -562,6 +566,16 @@ object CheckUnused:
562566
checkExplicit()
563567
end checkParam
564568

569+
// does the param have an alias in a default arg method that is used?
570+
def usedByDefaultGetter(param: Symbol, meth: Symbol): Boolean =
571+
val cls = if meth.isConstructor then meth.enclosingClass.companionModule else meth.enclosingClass
572+
val MethName = meth.name
573+
cls.info.decls.exists: d =>
574+
d.name match
575+
case DefaultGetterName(MethName, _) =>
576+
d.paramSymss.exists(_.exists(p => p.name == param.name && infos.refs(p)))
577+
case _ => false
578+
565579
def checkImplicit(sym: Symbol, pos: SrcPos) =
566580
val m = sym.owner
567581
def allowed =
@@ -591,9 +605,12 @@ object CheckUnused:
591605
val checking =
592606
aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor)
593607
|| aliasSym.isAllOf(Protected | ParamAccessor, butNot = CaseAccessor) && m.owner.is(Given)
594-
if checking && !infos.refs(alias.symbol) then
608+
if checking
609+
&& !infos.refs(alias.symbol)
610+
&& !usedByDefaultGetter(sym, m)
611+
then
595612
warnAt(pos)(UnusedSymbol.implicitParams(aliasSym))
596-
else
613+
else if !usedByDefaultGetter(sym, m) then
597614
warnAt(pos)(UnusedSymbol.implicitParams(sym))
598615

599616
def checkLocal(sym: Symbol, pos: SrcPos) =

tests/warn/i15503e.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,4 @@ object UnwrapTyped:
9292
error("Compiler bug: `codeOf` was not evaluated by the compiler")
9393

9494
object `default usage`:
95-
def f(i: Int)(j: Int = i * 2) = j // warn I guess
95+
def f(i: Int)(j: Int = i * 2) = j // ~warn~ I guess (see tests/warn/i23349.scala)

tests/warn/i23349.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//> using options -Wunused:explicits,implicits
2+
3+
// An external class that doesn't get its own `copy` method.
4+
class Foo(val a: String, val b: Int)
5+
6+
//
7+
// Example 1: add `copy` method via an extension method.
8+
//
9+
extension (self: Foo)
10+
def copy(a: String = self.a, b: Int = self.b): Foo = Foo(a, b) // nowarn
11+
12+
//
13+
// Example 2: implement `copyFoo` with parameter groups.
14+
//
15+
def copyFoo(foo: Foo)(a: String = foo.a, b: Int = foo.b): Foo = Foo(a, b)
16+
17+
class C:
18+
def copyFoo(foo: Foo, bar: String)(a: String = foo.a, b: Int = foo.b)(c: String = bar): Foo = Foo(a, b) // warn c
19+
def copyUsing(using foo: Foo, bar: String)(a: String = foo.a, b: Int = foo.b)(c: String = bar): Foo = // warn c
20+
Foo(a, b)
21+
22+
class K(k: Int)(s: String = "*"*k):
23+
override val toString = s
24+
25+
class KU(using k: Int)(s: String = "*"*k):
26+
override val toString = s
27+
28+
class KK(s: String):
29+
def this(k: Int)(s: String = "*"*k) = this(s)
30+
override val toString = s

0 commit comments

Comments
 (0)