Skip to content

Commit

Permalink
Fix exception on sequence matching with drop (scala#21281)
Browse files Browse the repository at this point in the history
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: <no file>
        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
```
  • Loading branch information
KavinSatheeskumar authored Aug 6, 2024
1 parent 8e1e229 commit 8064536
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 2 deletions.
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/init/Objects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
118 changes: 118 additions & 0 deletions tests/init-global/pos/match-complete.scala
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 8064536

Please sign in to comment.