From 2466f16f6613caac430bfd9917431b01a4be4dbc Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 Oct 2023 17:05:41 +0100 Subject: [PATCH] Consider extension methods in Space isSameUnapply [Cherry-picked 5a8a0ed134aaf4acd2e9a3ec2e9269371a8a2460] --- .../tools/dotc/transform/patmat/Space.scala | 8 ++++-- tests/pos/i18601.scala | 19 ++++++++++++++ tests/pos/i18601b.scala | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i18601.scala create mode 100644 tests/pos/i18601b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index b17216d19b03..1dbbd5d15202 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -525,10 +525,14 @@ object SpaceEngine { * We assume that unapply methods are pure, but the same method may * be called with different prefixes, thus behaving differently. */ - def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean = + def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean = trace(i"isSameUnapply($tp1, $tp2)") { + def isStable(tp: TermRef) = + !tp.symbol.is(ExtensionMethod) // The "prefix" of an extension method may be, but the receiver isn't, so exclude + && tp.prefix.isStable // always assume two TypeTest[S, T].unapply are the same if they are equal in types - (tp1.prefix.isStable && tp2.prefix.isStable || tp1.symbol == defn.TypeTest_unapply) + (isStable(tp1) && isStable(tp2) || tp1.symbol == defn.TypeTest_unapply) && tp1 =:= tp2 + } /** Return term parameter types of the extractor `unapp`. * Parameter types of the case class type `tp`. Adapted from `unapplyPlan` in patternMatcher */ diff --git a/tests/pos/i18601.scala b/tests/pos/i18601.scala new file mode 100644 index 000000000000..63468e2d8c32 --- /dev/null +++ b/tests/pos/i18601.scala @@ -0,0 +1,19 @@ +//> using options -Werror +extension (sc: StringContext) + def m: StringContext = sc + def unapply(string: String): Option[String] = + val pattern = sc.parts.head + if string.length == pattern.length then Some(string) else None + +class Test: + def parse(x: PartialFunction[String, String]) = x + + val pf = parse { + case m"x$s" => s + case m"xx$s" => s // was: unreachable + } + + // proof that the second case isn't unreachable (matches "ab") + def t1 = pf.applyOrElse("a", _ => ".") // "a" + def t2 = pf.applyOrElse("ab", _ => ".") // "ab" + def t3 = pf.applyOrElse("abc", _ => ".") // "." diff --git a/tests/pos/i18601b.scala b/tests/pos/i18601b.scala new file mode 100644 index 000000000000..5646b909bd67 --- /dev/null +++ b/tests/pos/i18601b.scala @@ -0,0 +1,26 @@ +//> using options -Werror + +// like pos/i18601 +// but with a dedicated SC class +// that made the false positive redundancy warning go away + +extension (sc: StringContext) + def m: SC = SC(sc) + +class SC(sc: StringContext): + def unapply(string: String): Option[String] = + val pattern = sc.parts.head + if string.length == pattern.length then Some(string) else None + +class Test: + def parse(x: PartialFunction[String, String]) = x + + val pf = parse { + case m"x$s" => s + case m"xx$s" => s // was: not unreachable (as a counter-example) + } + + // proof that the second case isn't unreachable (matches "ab") + def t1 = pf.applyOrElse("a", _ => ".") // "a" + def t2 = pf.applyOrElse("ab", _ => ".") // "ab" + def t3 = pf.applyOrElse("abc", _ => ".") // "."