diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index e3d78e3c5707..1397b05ec3b5 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1108,6 +1108,8 @@ object RefChecks { * An extension method is hidden if it does not offer a parameter that is not subsumed * by the corresponding parameter of the member with the same name (or of all alternatives of an overload). * + * This check is suppressed if this method is an override. + * * For example, it is not possible to define a type-safe extension `contains` for `Set`, * since for any parameter type, the existing `contains` method will compile and would be used. * @@ -1125,31 +1127,32 @@ object RefChecks { * If the extension method is nullary, it is always hidden by a member of the same name. * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.) */ - def checkExtensionMethods(sym: Symbol)(using Context): Unit = if sym.is(Extension) then - extension (tp: Type) - def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType - def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes - def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false } - val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver - val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter - def hidden = - target.nonPrivateMember(sym.name) - .filterWithPredicate: - member => - val memberIsImplicit = member.info.hasImplicitParams - val paramTps = - if memberIsImplicit then methTp.stripPoly.firstParamTypes - else methTp.firstExplicitParamTypes - - paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || { - val memberParamTps = member.info.stripPoly.firstParamTypes - !memberParamTps.isEmpty - && memberParamTps.lengthCompare(paramTps) == 0 - && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m) - } - .exists - if !target.typeSymbol.denot.isAliasType && !target.typeSymbol.denot.isOpaqueAlias && hidden - then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos) + def checkExtensionMethods(sym: Symbol)(using Context): Unit = + if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then + extension (tp: Type) + def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType + def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes + def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false } + val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver + val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter + def hidden = + target.nonPrivateMember(sym.name) + .filterWithPredicate: + member => + val memberIsImplicit = member.info.hasImplicitParams + val paramTps = + if memberIsImplicit then methTp.stripPoly.firstParamTypes + else methTp.firstExplicitParamTypes + + paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || { + val memberParamTps = member.info.stripPoly.firstParamTypes + !memberParamTps.isEmpty + && memberParamTps.lengthCompare(paramTps) == 0 + && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m) + } + .exists + if !target.typeSymbol.denot.isAliasType && !target.typeSymbol.denot.isOpaqueAlias && hidden + then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos) end checkExtensionMethods /** Verify that references in the user-defined `@implicitNotFound` message are valid. diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 81661e87b84e..3ea8b550f160 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -30,6 +30,7 @@ strict-pattern-bindings-3.0-migration.scala i17186b.scala i11982a.scala i17255 +i17735.scala # Tree is huge and blows stack for printing Text i7034.scala diff --git a/tests/pos/ext-override.scala b/tests/pos/ext-override.scala new file mode 100644 index 000000000000..d08439e13c9a --- /dev/null +++ b/tests/pos/ext-override.scala @@ -0,0 +1,12 @@ +//> using options -Xfatal-warnings + +trait Foo[T]: + extension (x: T) + def hi: String + +class Bla: + def hi: String = "hi" +object Bla: + given Foo[Bla] with + extension (x: Bla) + def hi: String = x.hi diff --git a/tests/pos-special/fatal-warnings/i17735.scala b/tests/pos/i17735.scala similarity index 90% rename from tests/pos-special/fatal-warnings/i17735.scala rename to tests/pos/i17735.scala index f171d4a028f7..17fb31010a8a 100644 --- a/tests/pos-special/fatal-warnings/i17735.scala +++ b/tests/pos/i17735.scala @@ -1,4 +1,4 @@ -//> using options -Wvalue-discard +//> using options -Xfatal-warnings -Wvalue-discard import scala.collection.mutable import scala.annotation.nowarn @@ -21,4 +21,4 @@ object Foo: // here @nowarn is effective without -Wfatal-warnings (i.e. no warning) // But with -Wfatal-warnings we get an error messageBuilder.append("\n").append(s): @nowarn("msg=discarded non-Unit value*") - messageBuilder.result() \ No newline at end of file + messageBuilder.result() diff --git a/tests/pos-special/fatal-warnings/i17735a.scala b/tests/pos/i17735a.scala similarity index 90% rename from tests/pos-special/fatal-warnings/i17735a.scala rename to tests/pos/i17735a.scala index fe0ea7e6bc45..b4d91f8d25fc 100644 --- a/tests/pos-special/fatal-warnings/i17735a.scala +++ b/tests/pos/i17735a.scala @@ -1,4 +1,4 @@ -//> using options -Wvalue-discard -Wconf:msg=non-Unit:s +//> using options -Xfatal-warnings -Wvalue-discard -Wconf:msg=non-Unit:s import scala.collection.mutable import scala.annotation.nowarn diff --git a/tests/pos-special/fatal-warnings/i17741.scala b/tests/pos/i17741.scala similarity index 90% rename from tests/pos-special/fatal-warnings/i17741.scala rename to tests/pos/i17741.scala index 7171aab83e4b..aa32e5a573d4 100644 --- a/tests/pos-special/fatal-warnings/i17741.scala +++ b/tests/pos/i17741.scala @@ -1,4 +1,4 @@ -//> using options -Wnonunit-statement +//> using options -Xfatal-warnings -Wnonunit-statement class Node() class Elem( @@ -29,4 +29,4 @@ object Main { ) } }: @annotation.nowarn() -} \ No newline at end of file +} diff --git a/tests/pos-special/fatal-warnings/nowarnannot.scala b/tests/pos/nowarnannot.scala similarity index 66% rename from tests/pos-special/fatal-warnings/nowarnannot.scala rename to tests/pos/nowarnannot.scala index 26e9713d0543..1710ae34b56f 100644 --- a/tests/pos-special/fatal-warnings/nowarnannot.scala +++ b/tests/pos/nowarnannot.scala @@ -1,3 +1,5 @@ +//> using options -Xfatal-warnings -Wvalue-discard + case class F(i: Int) object Main {