Skip to content

Commit c044420

Browse files
authored
No warn implicit param of overriding method (#22901)
Fixes #22895 Fixes #22896 Follow-up to #22729 Follow-up to #22757 Since Scala 2, the check for unused parameters does not warn for an overriding method, since the signature is dictated by the overridden method. That behavior was intentionally omitted in 3.7 because it was introduced in Scala 2 before `@nowarn` and `@unused` were available. (The warning would be too noisy without mitigation.) A compiler option for these heuristics was tried in a previous commit, but reverted. (Compiler options are difficult to evolve once added.) So these commits restore this heuristic without further ado. An option for "strict mode" without allowances is future work. The other issue was that detecting an override in a Java parent did not work. The commit always checks `is(Override)` before searching for an overridden member, since it is not necessary to verify the modifier (which happens in RefChecks). Incorrect overriding would result in false negatives, which is acceptable since it will error later. Further asymmetry between checking implicit params and explicit class param accessors may or may not be dubious.
2 parents 6bd8a0f + f8d681f commit c044420

File tree

6 files changed

+71
-20
lines changed

6 files changed

+71
-20
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,7 @@ object SymDenotations {
19531953
case _ => NoSymbol
19541954

19551955
/** The explicitly given self type (self types of modules are assumed to be
1956-
* explcitly given here).
1956+
* explicitly given here).
19571957
*/
19581958
def givenSelfType(using Context): Type = classInfo.selfInfo match {
19591959
case tp: Type => tp

Diff for: compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

+26-17
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ object CheckUnused:
590590
end checkExplicit
591591
// begin
592592
if !infos.skip(m)
593-
&& !m.nextOverriddenSymbol.exists
593+
&& !m.isEffectivelyOverride
594594
&& !allowed
595595
then
596596
checkExplicit()
@@ -614,6 +614,7 @@ object CheckUnused:
614614
|| sym.info.isInstanceOf[RefinedType] // can't be expressed as a context bound
615615
if ctx.settings.WunusedHas.implicits
616616
&& !infos.skip(m)
617+
&& !m.isEffectivelyOverride
617618
&& !allowed
618619
then
619620
if m.isPrimaryConstructor then
@@ -891,41 +892,49 @@ object CheckUnused:
891892
inline def exists(p: Name => Boolean): Boolean = nm.ne(nme.NO_NAME) && p(nm)
892893
inline def isWildcard: Boolean = nm == nme.WILDCARD || nm.is(WildcardParamName)
893894

894-
extension (tp: Type)
895-
def importPrefix(using Context): Type = tp match
895+
extension (tp: Type)(using Context)
896+
def importPrefix: Type = tp match
896897
case tp: NamedType => tp.prefix
897898
case tp: ClassInfo => tp.prefix
898899
case tp: TypeProxy => tp.superType.normalizedPrefix
899900
case _ => NoType
900-
def underlyingPrefix(using Context): Type = tp match
901+
def underlyingPrefix: Type = tp match
901902
case tp: NamedType => tp.prefix
902903
case tp: ClassInfo => tp.prefix
903904
case tp: TypeProxy => tp.underlying.underlyingPrefix
904905
case _ => NoType
905-
def skipPackageObject(using Context): Type =
906+
def skipPackageObject: Type =
906907
if tp.typeSymbol.isPackageObject then tp.underlyingPrefix else tp
907-
def underlying(using Context): Type = tp match
908+
def underlying: Type = tp match
908909
case tp: TypeProxy => tp.underlying
909910
case _ => tp
910911

911912
private val serializationNames: Set[TermName] =
912913
Set("readResolve", "readObject", "readObjectNoData", "writeObject", "writeReplace").map(termName(_))
913914

914-
extension (sym: Symbol)
915-
def isSerializationSupport(using Context): Boolean =
915+
extension (sym: Symbol)(using Context)
916+
def isSerializationSupport: Boolean =
916917
sym.is(Method) && serializationNames(sym.name.toTermName) && sym.owner.isClass
917918
&& sym.owner.derivesFrom(defn.JavaSerializableClass)
918-
def isCanEqual(using Context): Boolean =
919+
def isCanEqual: Boolean =
919920
sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass))
920-
def isMarkerTrait(using Context): Boolean =
921+
def isMarkerTrait: Boolean =
921922
sym.isClass && sym.info.allMembers.forall: d =>
922923
val m = d.symbol
923924
!m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass)
924-
def isEffectivelyPrivate(using Context): Boolean =
925+
def isEffectivelyPrivate: Boolean =
925926
sym.is(Private, butNot = ParamAccessor)
926-
|| sym.owner.isAnonymousClass && !sym.nextOverriddenSymbol.exists
927+
|| sym.owner.isAnonymousClass && !sym.isEffectivelyOverride
928+
def isEffectivelyOverride: Boolean =
929+
sym.is(Override)
930+
||
931+
sym.canMatchInheritedSymbols && { // inline allOverriddenSymbols using owner.info or thisType
932+
val owner = sym.owner.asClass
933+
val base = if owner.classInfo.selfInfo != NoType then owner.thisType else owner.info
934+
base.baseClasses.drop(1).iterator.exists(sym.overriddenSymbol(_).exists)
935+
}
927936
// pick the symbol the user wrote for purposes of tracking
928-
inline def userSymbol(using Context): Symbol=
937+
inline def userSymbol: Symbol=
929938
if sym.denot.is(ModuleClass) then sym.denot.companionModule else sym
930939

931940
extension (sel: ImportSelector)
@@ -939,21 +948,21 @@ object CheckUnused:
939948
case untpd.Ident(nme.WILDCARD) => true
940949
case _ => false
941950

942-
extension (imp: Import)
951+
extension (imp: Import)(using Context)
943952
/** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */
944-
def isPrimaryClause(using Context): Boolean =
953+
def isPrimaryClause: Boolean =
945954
imp.srcPos.span.pointDelta > 0 // primary clause starts at `import` keyword with point at clause proper
946955

947956
/** Generated import of cases from enum companion. */
948-
def isGeneratedByEnum(using Context): Boolean =
957+
def isGeneratedByEnum: Boolean =
949958
imp.symbol.exists && imp.symbol.owner.is(Enum, butNot = Case)
950959

951960
/** Under -Wunused:strict-no-implicit-warn, avoid false positives
952961
* if this selector is a wildcard that might import implicits or
953962
* specifically does import an implicit.
954963
* Similarly, import of CanEqual must not warn, as it is always witness.
955964
*/
956-
def isLoose(sel: ImportSelector)(using Context): Boolean =
965+
def isLoose(sel: ImportSelector): Boolean =
957966
if ctx.settings.WunusedHas.strictNoImplicitWarn then
958967
if sel.isWildcard
959968
|| imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit))

Diff for: tests/warn/i15503f.scala

+16-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object ExampleWithoutWith:
4242
case '{ ${Expr(opt)} : Some[T] } => Some(opt)
4343
case _ => None
4444

45-
//absolving names on matches of quote trees requires consulting non-abstract types in QuotesImpl
45+
//nowarning names on matches of quote trees requires consulting non-abstract types in QuotesImpl
4646
object Unmatched:
4747
import scala.quoted.*
4848
def transform[T](e: Expr[T])(using Quotes): Expr[T] =
@@ -84,3 +84,18 @@ package givens:
8484
given namely: (x: X) => Y: // warn protected param to given class
8585
def doY = "8"
8686
end givens
87+
88+
object i22895:
89+
trait Test[F[_], Ev] {
90+
def apply[A, B](fa: F[A])(f: A => B)(using ev: Ev): F[B]
91+
}
92+
given testId: Test[[a] =>> a, Unit] =
93+
new Test[[a] =>> a, Unit] {
94+
def apply[A, B](fa: A)(f: A => B)(using ev: Unit): B = f(fa) // nowarn override
95+
}
96+
class C:
97+
def f(using s: String) = s.toInt
98+
class D(i: Int) extends C:
99+
override def f(using String) = compute(i) // nowarn override
100+
def g(using sss: String) = compute(i) // warn
101+
def compute(i: Int) = i * 42 // returning a class param is deemed trivial, make it non-trivial

Diff for: tests/warn/i22896/J.java

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
public class J {
3+
private int i = 42;
4+
public int i() { return 27; }
5+
public String i(int j) { return "hello, world"; }
6+
}

Diff for: tests/warn/i22896/s.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//> using options -Werror -Wunused:privates
2+
3+
def f =
4+
new J:
5+
override val i = -1 // nowarn, trust override
6+
7+
def g =
8+
new J:
9+
override def i = -1 // nowarn, trust override
10+
11+
def h =
12+
new J:
13+
override def i() = -1 // nowarn correctly matches signature

Diff for: tests/warn/unused-params.scala

+9-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ trait BadMix { self: InterFace =>
122122
a
123123
}
124124
override def call(a: Int,
125-
XXXX: String, // warn no longer excused because required by superclass
125+
XXXX: String, // no warn still excused because override (required by self type)
126126
c: Double): Int = {
127127
println(c)
128128
a
@@ -135,6 +135,14 @@ trait BadMix { self: InterFace =>
135135
def i(implicit s: String) = answer // warn
136136
}
137137

138+
trait ImplFace:
139+
self: InterFace =>
140+
def call(a: Int,
141+
b: String, // no warn required by self type
142+
c: Double): Int =
143+
println(c)
144+
a
145+
138146
class Unequal {
139147
override def equals(other: Any) = toString.nonEmpty // no warn (override)
140148
}

0 commit comments

Comments
 (0)