From ec9a060abac9d3e05b47f6897b878b55da03044f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 10 Jun 2025 13:32:45 -0700 Subject: [PATCH 1/4] Nowarn extension taking params --- .../dotty/tools/dotc/transform/CheckUnused.scala | 8 ++++---- tests/warn/i23349.scala | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 tests/warn/i23349.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 3078d5f07d28..f0b1b1a25036 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -544,11 +544,11 @@ 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) then diff --git a/tests/warn/i23349.scala b/tests/warn/i23349.scala new file mode 100644 index 000000000000..c694c72af864 --- /dev/null +++ b/tests/warn/i23349.scala @@ -0,0 +1,15 @@ +//> using options -Wunused:explicits + +// 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) // warn From 0cbe43bd63e4a89c50b372908e68fff400e4728e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 11 Jun 2025 08:35:45 -0700 Subject: [PATCH 2/4] Check if param was used in default arg getter --- .../dotty/tools/dotc/transform/CheckUnused.scala | 15 +++++++++++++-- tests/warn/i15503e.scala | 2 +- tests/warn/i23349.scala | 9 +++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index f0b1b1a25036..fca8d73649c1 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -530,7 +530,7 @@ 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 @@ -551,6 +551,7 @@ object CheckUnused: ) && !sym.name.isInstanceOf[DerivedName] && !ctx.platform.isMainMethod(m) + && !usedByDefaultGetter(sym, m) then warnAt(pos)(UnusedSymbol.explicitParams(sym)) end checkExplicit @@ -562,6 +563,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 = 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 = @@ -593,7 +604,7 @@ object CheckUnused: || aliasSym.isAllOf(Protected | ParamAccessor, butNot = CaseAccessor) && m.owner.is(Given) if checking && !infos.refs(alias.symbol) 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 index c694c72af864..ab1457994e43 100644 --- a/tests/warn/i23349.scala +++ b/tests/warn/i23349.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:explicits +//> using options -Wunused:explicits,implicits // An external class that doesn't get its own `copy` method. class Foo(val a: String, val b: Int) @@ -12,4 +12,9 @@ extension (self: Foo) // // Example 2: implement `copyFoo` with parameter groups. // -def copyFoo(foo: Foo)(a: String = foo.a, b: Int = foo.b): Foo = Foo(a, b) // warn +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) From c02dea2c5b2f1bb035bc3a9167e740142dc99c8d Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 11 Jun 2025 08:53:16 -0700 Subject: [PATCH 3/4] Check if class param was used in default arg getter --- .../src/dotty/tools/dotc/transform/CheckUnused.scala | 12 +++++++++--- tests/warn/i23349.scala | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index fca8d73649c1..3aeb6d8f2faa 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -535,7 +535,10 @@ object CheckUnused: 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)) @@ -565,7 +568,7 @@ object CheckUnused: // does the param have an alias in a default arg method that is used? def usedByDefaultGetter(param: Symbol, meth: Symbol): Boolean = - val cls = meth.enclosingClass + val cls = if meth.isPrimaryConstructor then meth.enclosingClass.companionModule else meth.enclosingClass val MethName = meth.name cls.info.decls.exists: d => d.name match @@ -602,7 +605,10 @@ 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 if !usedByDefaultGetter(sym, m) then warnAt(pos)(UnusedSymbol.implicitParams(sym)) diff --git a/tests/warn/i23349.scala b/tests/warn/i23349.scala index ab1457994e43..9ae7c291dd58 100644 --- a/tests/warn/i23349.scala +++ b/tests/warn/i23349.scala @@ -18,3 +18,9 @@ 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 From aef0a253a6dea551e5e1691924e691ecc666fe39 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 11 Jun 2025 09:07:40 -0700 Subject: [PATCH 4/4] Unused check of secondary constructor param --- compiler/src/dotty/tools/dotc/transform/CheckUnused.scala | 2 +- tests/warn/i23349.scala | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 3aeb6d8f2faa..b452f558f969 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -568,7 +568,7 @@ object CheckUnused: // 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.isPrimaryConstructor then meth.enclosingClass.companionModule else meth.enclosingClass + val cls = if meth.isConstructor then meth.enclosingClass.companionModule else meth.enclosingClass val MethName = meth.name cls.info.decls.exists: d => d.name match diff --git a/tests/warn/i23349.scala b/tests/warn/i23349.scala index 9ae7c291dd58..4d0fcfc759db 100644 --- a/tests/warn/i23349.scala +++ b/tests/warn/i23349.scala @@ -24,3 +24,7 @@ class K(k: Int)(s: String = "*"*k): 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