From 7d79c561d2f7cc0b831a072c0cd4f722a8246062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Rochala?= <48657087+rochala@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:23:13 +0100 Subject: [PATCH] Warn universal extensions on opaque types (#22502) Fixes #22232 Work also done by @hamzaremmal, @julian-a-avar-c and @nmcb Co-authored-by: Hamza Remmal --- .../dotty/tools/dotc/typer/RefChecks.scala | 4 +-- tests/warn/i22232.check | 28 ++++++++++++++++ tests/warn/i22232.scala | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/warn/i22232.check create mode 100644 tests/warn/i22232.scala diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 51929bf4fbc9..629d18168c2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1171,7 +1171,7 @@ object RefChecks { def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true) def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false } val explicitInfo = sym.info.explicit // consider explicit value params - val target = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver + val target = explicitInfo.firstParamTypes.head.typeSymbol.info // required for extension method, the putative receiver val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter def hidden = target.nonPrivateMember(sym.name) @@ -1200,7 +1200,7 @@ object RefChecks { sym.owner.info.member(getterName) if getterDenot.exists then report.warning(ExtensionHasDefault(sym), getterDenot.symbol.srcPos) - if !target.typeSymbol.isOpaqueAlias && !sym.nextOverriddenSymbol.exists && hidden + if !sym.nextOverriddenSymbol.exists && hidden then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos) end checkExtensionMethods diff --git a/tests/warn/i22232.check b/tests/warn/i22232.check new file mode 100644 index 000000000000..cf3d6d4e004e --- /dev/null +++ b/tests/warn/i22232.check @@ -0,0 +1,28 @@ +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:3:23 -------------------------------------------------------- +3 | extension (c: C) def equals(that: Any): Boolean = false // warn + | ^ + | Extension method equals will never be selected from type C + | because C 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:9:25 -------------------------------------------------------- +9 | extension (d: D) def equals(that: Any): Boolean = false // warn + | ^ + | Extension method equals will never be selected from type C + | because C 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:13:38 ------------------------------------------------------- +13 | extension (arr: MyString[Byte]) 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. + | + | 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 + | ^ + | Extension method length will never be selected from type String + | because String already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i22232.scala b/tests/warn/i22232.scala new file mode 100644 index 000000000000..79b8317a7329 --- /dev/null +++ b/tests/warn/i22232.scala @@ -0,0 +1,32 @@ +class C +object C: + extension (c: C) def equals(that: Any): Boolean = false // warn + +object X: + class C + opaque type D <: C = C + object D: + extension (d: D) def equals(that: Any): Boolean = false // warn + +object Upperbound1: + opaque type MyString[+T] <: String = String + extension (arr: MyString[Byte]) def length: Int = 0 // warn + +object Upperbound2: + opaque type MyString[+T] <: String = String + extension [T <: MyString[Byte]](arr: T) def length: Int = 0 // warn + +object Upperbound3: + opaque type MyString[+T] <: String = String + extension [T](arr: T) def length: Int = 0 // nowarn + +object NonUpperbound1: + opaque type MyString[+T] = String + extension (arr: MyString[Byte]) def length: Int = 0 // nowarn +object NonUpperbound2: + opaque type MyString[+T] = String + extension [T <: MyString[Byte]](arr: T) def length2: Int = 0 // nowarn + +object NonUpperbound3: + opaque type MyString[+T] = String + extension [T](arr: T) def length: Int = 0 // nowarn