diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 3078d5f07d28..b452f558f969 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -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)) @@ -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 @@ -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 = @@ -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) = diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index 63a9dea163d4..0a7fadc7189a 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -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) diff --git a/tests/warn/i23349.scala b/tests/warn/i23349.scala new file mode 100644 index 000000000000..4d0fcfc759db --- /dev/null +++ b/tests/warn/i23349.scala @@ -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