scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

Compiler crash using Tuple :* syntax for a NamedTuple in unapply

Open eejbyfeldt opened this issue 8 months ago • 2 comments

Compiler version

3.7.0 and 3.7.1-RC1

Minimized code

object Unpack {
  def unapply(e: (Int, Int)): Some[NamedTuple.NamedTuple["_1" *: "_2" *: EmptyTuple, Int *: Int *: EmptyTuple]] = ???

  def select[T, R](f: T => R) = ???
  select[(Int, Int), Int] { case Unpack(first, _) => first }
}

Output (click arrow to expand)


  unhandled exception while running MegaPhase{protectedAccessors, extmethods, uncacheGivenAliases, checkStatic, elimByName, hoistSuperArgs, forwardDepChecks, specializeApplyMethods, tryCatchPatterns, patternMatcher} on extractor_namedtuple.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: extractor_namedtuple.scala
        during phase: MegaPhase{protectedAccessors, extmethods, uncacheGivenAliases, checkStatic, elimByName, hoistSuperArgs, forwardDepChecks, specializeApplyMethods, tryCatchPatterns, patternMatcher}
                mode: Mode(ImplicitsEnabled)
     library version: version 2.13.16
    compiler version: version 3.7.0
            settings: 

Exception in thread "main" scala.MatchError: List() (of class scala.collection.immutable.Nil$)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.matchArgsPatternPlan$1(PatternMatcher.scala:270)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.matchArgsComponentsPlan$1(PatternMatcher.scala:265)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.matchArgsPlan$1(PatternMatcher.scala:276)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.$anonfun$13(PatternMatcher.scala:403)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.letAbstract(PatternMatcher.scala:117)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.unapplyPlan$1$$anonfun$1(PatternMatcher.scala:388)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.letAbstract(PatternMatcher.scala:117)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.unapplyPlan$1(PatternMatcher.scala:360)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.patternPlan(PatternMatcher.scala:444)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.caseDefPlan(PatternMatcher.scala:480)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.matchPlan$$anonfun$1$$anonfun$1(PatternMatcher.scala:487)
	at scala.collection.immutable.List.foldRight(List.scala:353)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.matchPlan$$anonfun$1(PatternMatcher.scala:486)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.letAbstract(PatternMatcher.scala:117)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.matchPlan(PatternMatcher.scala:484)
	at dotty.tools.dotc.transform.PatternMatcher$Translator.translateMatch(PatternMatcher.scala:1072)
	at dotty.tools.dotc.transform.PatternMatcher.transformMatch(PatternMatcher.scala:47)
	at dotty.tools.dotc.transform.MegaPhase.goMatch(MegaPhase.scala:816)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:383)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
	at dotty.tools.dotc.transform.MegaPhase.mapDefDef$1(MegaPhase.scala:265)
	at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:268)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
	at dotty.tools.dotc.transform.MegaPhase.loop$2(MegaPhase.scala:471)
	at dotty.tools.dotc.transform.MegaPhase.transformBlock(MegaPhase.scala:476)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:315)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
	at dotty.tools.dotc.transform.MegaPhase.loop$3(MegaPhase.scala:486)
	at dotty.tools.dotc.transform.MegaPhase.transformTrees(MegaPhase.scala:486)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:296)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
	at dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
	at dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:376)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
	at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:272)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
	at dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
	at dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
	at dotty.tools.dotc.transform.MegaPhase.mapPackage$1(MegaPhase.scala:396)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:399)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
	at dotty.tools.dotc.transform.MegaPhase.transformUnit(MegaPhase.scala:481)
	at dotty.tools.dotc.transform.MegaPhase.run(MegaPhase.scala:493)
	at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:383)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:334)
	at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:376)
	at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:367)
	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:1324)
	at dotty.tools.dotc.Run.runPhases$1(Run.scala:360)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1$$anonfun$2(Run.scala:407)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1$$anonfun$adapted$1(Run.scala:407)
	at scala.Function0.apply$mcV$sp(Function0.scala:42)
	at dotty.tools.dotc.Run.showProgress(Run.scala:469)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:407)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:419)
	at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:69)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:419)
	at dotty.tools.dotc.Run.compileSources(Run.scala:306)
	at dotty.tools.dotc.Run.compile(Run.scala:291)
	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.MainGenericCompiler$.run$1(MainGenericCompiler.scala:162)
	at dotty.tools.MainGenericCompiler$.main(MainGenericCompiler.scala:186)
	at dotty.tools.MainGenericCompiler.main(MainGenericCompiler.scala)

eejbyfeldt avatar May 14 '25 09:05 eejbyfeldt

It's not clear to me if the :* syntax for tuples should behave the same as (T_1, ... T_n) (or Tuple_n[...]) for pattern matching, since it doesn't for regular tuples, but the spec states there is a type-level equivalence: https://scala-lang.org/files/archive/spec/3.4/03-types.html#tuple-types so if they behave differently it should be documented.

def unapply(e: (Int, Int)): Some[Int *: Int *: EmptyTuple] = ??? // fails with
-- [E107] Syntax Error: tests/pos/i23156.scala:12:39 ---------------------------
12 |  select[(Int, Int), Int] { case Unpack(first, _) => first }
   |                                 ^^^^^^^^^^^^^^^^
   |    Wrong number of argument patterns for Unpack; expected: ((Int, Int))

def unapply(e: Int *: Int *: EmptyTuple): Some[Int *: Int *: EmptyTuple] = ??? // same error, even with select updated
-- [E107] Syntax Error: tests/pos/i23156.scala:12:53 ---------------------------
12 |  select[Int *: Int *: EmptyTuple, Int] { case Unpack(first, _) => first }
   |                                               ^^^^^^^^^^^^^^^^
   |    Wrong number of argument patterns for Unpack; expected: ((Int, Int))
   |
   | longer explanation available when compiling with `-explain`

def unapply(e: (Int, Int)): Some[(Int, Int)] = ??? // ok

def unapply(e: (Int, Int)): Some[NamedTuple.NamedTuple[("x", "y"), (Int, Int)]] // ok

def unapply(e: (Int, Int)): Some[(x: Int, y: Int)] // ok

aherlihy avatar Jun 12 '25 15:06 aherlihy

From discussion with the compiler team the behavior should mimic the regular tuples and fail with a mismatch error.

aherlihy avatar Jun 18 '25 12:06 aherlihy