diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 7037a9a986aa..8083d95c7307 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1159,28 +1159,35 @@ object RefChecks { def checkExtensionMethods(sym: Symbol)(using Context): Unit = if sym.is(Extension) then atPhase(typerPhase): extension (tp: Type) - def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true) - def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false } - def isParamLess = tp.stripPoly match { case mt: MethodType => false case _ => true } + def explicit = Applications.stripImplicit(tp.widen, wildcardOnly = true) + def hiBoundOpaque = + if tp.typeSymbol.isOpaqueAlias then + tp.typeSymbol.info match + case TypeBounds(lo, hi) if lo ne hi => hi + case _ => tp + else tp.hiBound val explicitInfo = sym.info.explicit // consider explicit value params def memberHidesMethod(member: Denotation): Boolean = - val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter - if methTp.isParamLess then - return true // extension without parens is always hidden by a member of same name - val memberIsImplicit = member.info.hasImplicitParams - inline def paramsCorrespond = + val methTp = explicitInfo.stripPoly.resultType // skip leading implicits and the "receiver" parameter + val memberIsImplicit = member.info.isImplicitMethod + // are the params of the extension subsumed by the params of the member? + // an unbounded type param always subsumes. + // the params must have the same opacity to conclude the extension is hidden. + inline def paramsCorrespond = { val paramTps = - if memberIsImplicit then methTp.stripPoly.firstParamTypes + if memberIsImplicit then methTp.firstParamTypes else methTp.explicit.firstParamTypes - val memberParamTps = member.info.stripPoly.firstParamTypes + val memberParamTps = member.info.firstParamTypes memberParamTps.corresponds(paramTps): (m, x) => m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias - && (x frozen_<:< m) - memberIsImplicit && !methTp.hasImplicitParams || paramsCorrespond + && (m.isInstanceOf[ParamRef] && (m eq m.hiBound) || (x.hiBound frozen_<:< m.hiBound)) + } + methTp.isParameterless // extension without parens is always hidden by a member of same name + || memberIsImplicit && !methTp.isImplicitMethod // see above + || paramsCorrespond // match by type and opacity def targetOfHiddenExtension: Symbol = - val target = - val target0 = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver - target0.dealiasKeepOpaques.typeSymbol.info + val target = explicitInfo.firstParamTypes.head // required for extension method; the nominal receiver + .hiBound.typeSymbol.typeRef.dealiasKeepOpaques.hiBoundOpaque val member = target.nonPrivateMember(sym.name) .filterWithPredicate: member => member.symbol.isPublic && memberHidesMethod(member) diff --git a/tests/warn/i22232.check b/tests/warn/i22232.check index cf3d6d4e004e..d07f11aaf58e 100644 --- a/tests/warn/i22232.check +++ b/tests/warn/i22232.check @@ -19,8 +19,15 @@ | because String already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` --- [E194] Potential Issue Warning: tests/warn/i22232.scala:17:46 ------------------------------------------------------- -17 | extension [T <: MyString[Byte]](arr: T) def length: Int = 0 // warn +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:21:39 ------------------------------------------------------- +21 | extension (arr: MyStuff[String]) def add(s: String): MyStuff[String] = ??? // warn + | ^ + | Extension method add will never be selected from type MyList + | because MyList already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:25:46 ------------------------------------------------------- +25 | extension [T <: MyString[Byte]](arr: T) def length: Int = 0 // warn | ^ | Extension method length will never be selected from type String | because String already has a member with the same name and compatible parameter types. diff --git a/tests/warn/i22232.scala b/tests/warn/i22232.scala index f94e413920a2..e39610e4666f 100644 --- a/tests/warn/i22232.scala +++ b/tests/warn/i22232.scala @@ -12,6 +12,14 @@ object Upperbound1: opaque type MyString[+T] <: String = String extension (arr: MyString[Byte]) def length: Int = 0 // warn +object Upperbound1a: + trait MyList[+A]: + def add[B >: A](x: B): MyList[B] + class MyListImpl[+A] extends MyList[A]: + def add[B >: A](x: B): MyList[B] = ??? + opaque type MyStuff[+T] <: MyList[T] = MyListImpl[T] + extension (arr: MyStuff[String]) def add(s: String): MyStuff[String] = ??? // warn + object Upperbound2: opaque type MyString[+T] <: String = String extension [T <: MyString[Byte]](arr: T) def length: Int = 0 // warn diff --git a/tests/warn/i23666.scala b/tests/warn/i23666.scala new file mode 100644 index 000000000000..d35fd0633b81 --- /dev/null +++ b/tests/warn/i23666.scala @@ -0,0 +1,15 @@ + +type Tuple = scala.Tuple + +infix type =:= [A, B] = A => B + +object `=:=` : + given [A] => A =:= A = a => a + +extension [T <: Tuple] (tuple: T) + + def reverse[A, B](using ev: T =:= (A, B)): (B, A) = // warn + val ab = ev(tuple) + (ab._2, ab._1) + + def toList: List[Nothing] = Nil // warn