From b093ae8fc8b624aaba65fa89a29bb8bad4291708 Mon Sep 17 00:00:00 2001 From: Kavin Satheeskumar <71559920+KavinSatheeskumar@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:27:50 -0400 Subject: [PATCH] Fix exception on sequence matching with drop (#21281) When using scalac with the flag Ysafe-init-global to compile the following example, I get the following exception. This PR fixes it and adds a test case to catch things like this in the future. Note, I made this pull request in collaboration with Ondrej Lhotak, Enze Xing, Fengyun Liu and David Hua) The example ```Scala object Matcher { val vararg_arr = Array(0, 1, 2, 3) val vararg_lst = List(vararg_arr*) val vararg_splice = vararg_lst match case List(0, 1, xs*) => 1 // binds xs to Seq(2, 3) case List(1, _*) => 0 // wildcard pattern case _ => 2 println(vararg_splice) } ``` The exception ``` Exception in thread "main" java.lang.AssertionError: NoDenotation.owner at dotty.tools.dotc.core.SymDenotations$NoDenotation$.owner(SymDenotations.scala:2623) at dotty.tools.dotc.transform.init.Objects.call(Objects.scala:660) at dotty.tools.dotc.transform.init.Objects.evalSeqPatterns$1(Objects.scala:1494) at dotty.tools.dotc.transform.init.Objects.evalPattern$1(Objects.scala:1406) at dotty.tools.dotc.transform.init.Objects.evalCase$1(Objects.scala:1336) at dotty.tools.dotc.transform.init.Objects.patternMatch$$anonfun$1(Objects.scala:1505) at scala.collection.immutable.List.map(List.scala:247) at dotty.tools.dotc.transform.init.Objects.patternMatch(Objects.scala:1505) at dotty.tools.dotc.transform.init.Objects.cases(Objects.scala:1255) at dotty.tools.dotc.transform.init.Objects.eval$$anonfun$1(Objects.scala:1095) at dotty.tools.dotc.transform.init.Objects$Cache$Data.$anonfun$5(Objects.scala:539) at dotty.tools.dotc.transform.init.Cache.cachedEval(Cache.scala:112) at dotty.tools.dotc.transform.init.Objects$Cache$Data.cachedEval(Objects.scala:538) at dotty.tools.dotc.transform.init.Objects.eval(Objects.scala:1095) at dotty.tools.dotc.transform.init.Objects.init$$anonfun$4(Objects.scala:1708) at scala.collection.immutable.List.foreach(List.scala:334) at dotty.tools.dotc.transform.init.Objects.init(Objects.scala:1705) at dotty.tools.dotc.transform.init.Objects$State$.iterate$1(Objects.scala:278) at dotty.tools.dotc.transform.init.Objects$State$.doCheckObject(Objects.scala:293) at dotty.tools.dotc.transform.init.Objects$State$.checkObjectAccess(Objects.scala:320) at dotty.tools.dotc.transform.init.Objects.accessObject(Objects.scala:1059) at dotty.tools.dotc.transform.init.Objects.checkClasses$$anonfun$2(Objects.scala:1072) at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:619) at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:617) at scala.collection.AbstractIterable.foreach(Iterable.scala:935) at scala.collection.IterableOps$WithFilter.foreach(Iterable.scala:905) at dotty.tools.dotc.transform.init.Objects.checkClasses(Objects.scala:1070) at dotty.tools.dotc.transform.init.Checker.runOn$$anonfun$1(Checker.scala:58) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) at dotty.tools.dotc.core.Phases$Phase.cancellable(Phases.scala:521) at dotty.tools.dotc.transform.init.Checker.runOn(Checker.scala:59) at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:343) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323) at dotty.tools.dotc.Run.runPhases$1(Run.scala:336) at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:384) at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:396) at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:69) at dotty.tools.dotc.Run.compileUnits(Run.scala:396) at dotty.tools.dotc.Run.compileSources(Run.scala:282) at dotty.tools.dotc.Run.compile(Run.scala:267) at dotty.tools.dotc.Driver.doCompile(Driver.scala:37) at dotty.tools.dotc.Driver.process(Driver.scala:201) at dotty.tools.dotc.Driver.process(Driver.scala:169) at dotty.tools.dotc.Driver.process(Driver.scala:181) at dotty.tools.dotc.Driver.main(Driver.scala:211) at dotty.tools.dotc.Main.main(Main.scala) exception occurred while compiling List(../test-case.scala) An unhandled exception was thrown in the compiler. Please file a crash report here: https://github.com/scala/scala3/issues/new/choose For non-enriched exceptions, compile with -Xno-enrich-error-messages. while compiling: during phase: parser mode: Mode(ImplicitsEnabled,ReadPositions) library version: version 2.13.14 compiler version: version 3.6.0-RC1-bin-SNAPSHOT-nonbootstrapped-git-3dfd762 settings: -Ysafe-init-global true -classpath /home/kavin/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.14/scala-library-2.13.14.jar:/home/kavin/Documents/4A/URA2/scala3/library/../out/bootstrap/scala3-library-bootstrapped/scala-3.6.0-RC1-bin-SNAPSHOT-nonbootstrapped/scala3-library_3-3.6.0-RC1-bin-SNAPSHOT.jar -d /home/kavin/Documents/4A/URA2/scala3/compiler/../out/default-last-scalac-out.jar [error] Nonzero exit code returned from runner: 1 [error] (scala3-compiler / Compile / runMain) Nonzero exit code returned from runner: 1 [error] Total time: 17 s, completed Jul 26, 2024, 6:35:43 PM ``` [Cherry-picked 8064536e507680888285ccebba685be31cb5963a] --- .../tools/dotc/transform/init/Objects.scala | 4 +- tests/init-global/pos/match-complete.scala | 118 ++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 tests/init-global/pos/match-complete.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index bfa684eef8b4..1ceb8d4472a3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1485,12 +1485,12 @@ class Objects(using Context @constructorOnly): if isWildcardStarArgList(pats) then if pats.size == 1 then // call .toSeq - val toSeqDenot = scrutineeType.member(nme.toSeq).suchThat(_.info.isParameterless) + val toSeqDenot = getMemberMethod(scrutineeType, nme.toSeq, toSeqType(elemType)) val toSeqRes = call(scrutinee, toSeqDenot.symbol, Nil, scrutineeType, superType = NoType, needResolve = true) evalPattern(toSeqRes, pats.head) else // call .drop - val dropDenot = getMemberMethod(scrutineeType, nme.drop, applyType(elemType)) + val dropDenot = getMemberMethod(scrutineeType, nme.drop, dropType(elemType)) val dropRes = call(scrutinee, dropDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) for pat <- pats.init do evalPattern(applyRes, pat) evalPattern(dropRes, pats.last) diff --git a/tests/init-global/pos/match-complete.scala b/tests/init-global/pos/match-complete.scala new file mode 100644 index 000000000000..eb0e4481f59e --- /dev/null +++ b/tests/init-global/pos/match-complete.scala @@ -0,0 +1,118 @@ +object Matcher { + // Chained Match + val chained_match_xs: List[Any] = List(1, 2, 3) + val chained_match_x = chained_match_xs match { + case Nil => "empty" + case _ => "nonempty" + } match { + case "empty" => 0 + case "nonempty" => 1 + } + println(chained_match_x) + + // Vararg Splices + val vararg_arr = Array(0, 1, 2, 3) + val vararg_lst = List(vararg_arr*) // vararg splice argument + // Throws an exception? + val vararg_splice = vararg_lst match + case List(0, 1, xs*) => 1 // binds xs to Seq(2, 3) + case List(1, _*) => 0 // wildcard pattern + case _ => 2 + println(vararg_splice) + println(vararg_lst) + + // Pattern Definitions + val patter_def_xs: List[Any] = List(1, 2, 3) + val (patter_def_x: Any) :: _ = patter_def_xs : @unchecked + println(patter_def_x) + + val patter_def_pair = (1, true) + val (patter_def_a, patter_def_b) = patter_def_pair + println(patter_def_a) + + val elems: List[(Int, Int)] = List((1, 2), (3, 4), (5, 6)) + + for ((x,y) <- elems) do println(x) + + def main(args: Array[String]) = { + // println(chained_match_x) + println(vararg_splice) + // println(patter_def_x) + // println( + } +} + + +// Patter Matching Using Extractors + +// Option Extractors +case class Person(name: String, age: Int) +object Person { + def unapply(person: Person): Option[(String, Int)] = Some((person.name, person.age)) +} + +object OptionMatcher { + val person = Person("Alice", 25) + + val result = person match { + case Person(name, age) => s"Name: $name, Age: $age" + case _ => "Not a person" + } + println(result) +} + + + +// Boolean Extractors +object Adult { + def unapply(person: Person): Boolean = person.age >= 18 +} + +object BooleanMatcher { + val person = Person("Charlie", 17) + + val adultResult = person match { + case Adult() => s"${person.name} is an adult" + case _ => s"${person.name} is not an adult" + } + + println(adultResult) +} + + + +// Variadic Extractors +// Add cases for exceptions +// +// Adding some warning test cases +// - + +object VariadicExtractor { + // Define an unapply method that takes a List and returns an Option of Seq + def unapplySeq[A](list: List[A]): Option[Seq[A]] = Some(list) +} + +object PatternMatchExample extends App { + def describeList(list: List[Int]): String = list match { + case VariadicExtractor(1, 2, rest @ _*) => + s"Starts with 1, 2 followed by: ${rest.mkString(", ")}" + case VariadicExtractor(1, rest @ _*) => + s"Starts with 1 followed by: ${rest.mkString(", ")}" + case VariadicExtractor(first, second, rest @ _*) => + s"Starts with $first, $second followed by: ${rest.mkString(", ")}" + case VariadicExtractor(single) => + s"Only one element: $single" + case VariadicExtractor() => + "Empty list" + case _ => + "Unknown pattern" + } + + // Test cases + println(describeList(List(1, 2, 3, 4, 5))) // Output: Starts with 1, 2 followed by: 3, 4, 5 + println(describeList(List(1, 3, 4, 5))) // Output: Starts with 1 followed by: 3, 4, 5 + println(describeList(List(2, 3, 4, 5))) // Output: Starts with 2, 3 followed by: 4, 5 + println(describeList(List(1))) // Output: Only one element: 1 + println(describeList(List())) // Output: Empty list +} +