From 80e19f64608e5fb7c66f31d0b32364313ed467f0 Mon Sep 17 00:00:00 2001
From: Hamza Remmal <hamza@remmal.net>
Date: Sun, 23 Feb 2025 19:58:42 +0100
Subject: [PATCH] Warn universal extensions on opaque types

---
 .../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 d96b37dd3c55..43a2cc9d0b32 100644
--- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala
+++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala
@@ -1168,7 +1168,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)
@@ -1197,7 +1197,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