Skip to content

Commit 3ea7f7a

Browse files
authored
Consider extension methods in Space isSameUnapply (#18642)
2 parents 138eb55 + 5a8a0ed commit 3ea7f7a

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,10 +516,14 @@ object SpaceEngine {
516516
* We assume that unapply methods are pure, but the same method may
517517
* be called with different prefixes, thus behaving differently.
518518
*/
519-
def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean =
519+
def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean = trace(i"isSameUnapply($tp1, $tp2)") {
520+
def isStable(tp: TermRef) =
521+
!tp.symbol.is(ExtensionMethod) // The "prefix" of an extension method may be, but the receiver isn't, so exclude
522+
&& tp.prefix.isStable
520523
// always assume two TypeTest[S, T].unapply are the same if they are equal in types
521-
(tp1.prefix.isStable && tp2.prefix.isStable || tp1.symbol == defn.TypeTest_unapply)
524+
(isStable(tp1) && isStable(tp2) || tp1.symbol == defn.TypeTest_unapply)
522525
&& tp1 =:= tp2
526+
}
523527

524528
/** Return term parameter types of the extractor `unapp`.
525529
* Parameter types of the case class type `tp`. Adapted from `unapplyPlan` in patternMatcher */

tests/pos/i18601.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//> using options -Werror
2+
extension (sc: StringContext)
3+
def m: StringContext = sc
4+
def unapply(string: String): Option[String] =
5+
val pattern = sc.parts.head
6+
if string.length == pattern.length then Some(string) else None
7+
8+
class Test:
9+
def parse(x: PartialFunction[String, String]) = x
10+
11+
val pf = parse {
12+
case m"x$s" => s
13+
case m"xx$s" => s // was: unreachable
14+
}
15+
16+
// proof that the second case isn't unreachable (matches "ab")
17+
def t1 = pf.applyOrElse("a", _ => ".") // "a"
18+
def t2 = pf.applyOrElse("ab", _ => ".") // "ab"
19+
def t3 = pf.applyOrElse("abc", _ => ".") // "."

tests/pos/i18601b.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//> using options -Werror
2+
3+
// like pos/i18601
4+
// but with a dedicated SC class
5+
// that made the false positive redundancy warning go away
6+
7+
extension (sc: StringContext)
8+
def m: SC = SC(sc)
9+
10+
class SC(sc: StringContext):
11+
def unapply(string: String): Option[String] =
12+
val pattern = sc.parts.head
13+
if string.length == pattern.length then Some(string) else None
14+
15+
class Test:
16+
def parse(x: PartialFunction[String, String]) = x
17+
18+
val pf = parse {
19+
case m"x$s" => s
20+
case m"xx$s" => s // was: not unreachable (as a counter-example)
21+
}
22+
23+
// proof that the second case isn't unreachable (matches "ab")
24+
def t1 = pf.applyOrElse("a", _ => ".") // "a"
25+
def t2 = pf.applyOrElse("ab", _ => ".") // "ab"
26+
def t3 = pf.applyOrElse("abc", _ => ".") // "."

0 commit comments

Comments
 (0)