Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes from 3.4.0 breaks match exhaustive checking of a macros #22212

Open
HollandDM opened this issue Dec 15, 2024 · 8 comments
Open

Changes from 3.4.0 breaks match exhaustive checking of a macros #22212

HollandDM opened this issue Dec 15, 2024 · 8 comments
Assignees
Labels
area:inline area:metaprogramming:other Issues tied to metaprogramming/macros not covered by the other labels. area:reporting Error reporting including formatting, implicit suggestions, etc area:scala.js itype:bug regression This worked in a previous version but doesn't anymore stat:needs bisection Need to use nightly builds and git bisect to find out the commit where this issue was introduced

Comments

@HollandDM
Copy link

HollandDM commented Dec 15, 2024

Compiler version

Airstream released v17.2.0, which introduce a macro that help users to "split" signal of a ADT into signal of its children.
This macros re-arranges case blocks and create a match expr out of it, there for leverages the compiler's exhaustive checking.
It works on 3.3.4, but stop working on >=3.4.0

Minimized code (required Airstream v17.2.0)

import com.raquo.laminar.api.L.*

object Main {

  sealed abstract class Page {}

  sealed abstract class ToolPage extends Page {}

  case object CompilePage extends ToolPage {}

  case object AsmEmulatePage extends ToolPage {}

  case object BugHuntersPage extends Page {}

  def testSignal(pageSignal: Signal[ToolPage]): Unit = {
    pageSignal
      .splitMatchOne
      .handleValue(CompilePage)(())
      .toSignal
  }

  def main(args: Array[String]): Unit = ()

}

Expectation

on scala 3.3.4 LTS, compiler will warn "match may not exhaustive".
it should remains so on >=3.4

UPDATED: further checking yields the following results:
3.3.4 works, 3.4.0 doesn't, 3.5.0 doesn't, 3.5.1 works, 3.5.2 works, 3.6.2 doesn't

@HollandDM HollandDM added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 15, 2024
@HollandDM
Copy link
Author

seems like this is the cause, what can we do in this situation?

@j-mie6
Copy link

j-mie6 commented Dec 15, 2024

I can understand why you wouldn't want the compiler generating warnings for macro code that you don't necessarily have any control over as a user, but that could be done by macro authors with an @unchecked annotation anyway.

@yurique
Copy link

yurique commented Dec 16, 2024

while looking into this I used the -Xprint:inline scalac option and I can see the generated code is very different (and it shows why 3.6.2 won't do the exhaustiveness checks - there's no match at all, in contrast to 3.5.1)

(I cleaned up parts of the printed code to make it more legible, so it might not be 100% accurate, but it shows the idea):

Source:

  enum Test {

    case Case1
    case Case2
    case Case3(value: String)

  }

  val s: Signal[Test] = Val(Test.Case2)

  // ...
  s.splitMatchOne
   .handleType[Test.Case3].apply { (init, sig) =>
//     ...
   }
   .toSignal

Scala 3.5.1:

MyModule.s.map[
(Int, Any)](
{
  def $anonfun(
    i: MyModule.Test)
    : (Int, Any) =
    i match // a REAL match
      {
        case t @_: MyModule.Test.Case3 =>
          {
            val res: Any =
              t:
                  MyModule
                    .Test.Case3

            Tuple2.apply[Int, Any](0,res)
          }
      }
  closure($anonfun)
}

Scala 3.6.2:

def $anonfun(
  i: MyModule.Test
): (Int, Any) =
  matchResult5[(Int, Any)]:
    {
      case val x6:

          (i : MyModule.Test) = i
      if // an if-else instead of a match
        x6.$isInstanceOf[MyModule.Test.Case3] 
       then
        {
          case val t: MyModule.Test.Case3
           = x6.$asInstanceOf[MyModule.Test.Case3]
          return[matchResult5]
            {
              val res: Any =
                t:
                    MyModule
                      .Test.
                      Case3

              Tuple2.apply[Int, Any](0, res)
            }
        }
       else ()
      throw new MatchError(x6)
      }
  closure($anonfun)

@j-mie6
Copy link

j-mie6 commented Dec 16, 2024

Seems to me (from the debug) that a desugaring stage is being ran sooner than we'd like on the match modes... The splicing of a quote should always produce legal scala code, but this clearly isn't (case val, anyone?). Perhaps this arose from the improvements to for-comprehensions, perhaps? Definitely feels like a bug to me

@yurique
Copy link

yurique commented Dec 16, 2024

I also tried adding more "cases" to make sure it's not an edge case optimization - and no, it just results in more if ... return else ()

case val x6:

  (i : MyModule.Test) = i
  
  if x6.$isInstanceOf[MyModule.Test.Case3] 
  then
  // ...
  return // ...
  else ()

  if x6.eq(MyModule.Test.Case1)
  then
  // ...
  return // ...
  else ()

  throw new MatchError(x6)

P.S. (i : MyModule.Test) = i doesn't look like valid scala code, like case val

@Gedochao Gedochao added area:inline stat:needs minimization Needs a self contained minimization regression This worked in a previous version but doesn't anymore stat:needs bisection Need to use nightly builds and git bisect to find out the commit where this issue was introduced area:metaprogramming:other Issues tied to metaprogramming/macros not covered by the other labels. and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 16, 2024
@Gedochao Gedochao added the area:reporting Error reporting including formatting, implicit suggestions, etc label Dec 16, 2024
@Gedochao
Copy link
Contributor

Gedochao commented Dec 16, 2024

3.3.4 works, 3.4.0 doesn't, 3.5.0 doesn't, 3.5.1 works, 3.5.2 works, 3.6.2 doesn't

Ugh. 😕
BTW it also works fine in 3.3.5-RC1, in case anyone wonders.

Also, please provide the definition of the dependencies/build when raising issues.
Here's what seems like the full repro with Scala CLI:

//> using dep com.raquo::airstream::17.2.0
//> using dep com.raquo::laminar::17.2.0
//> using platform js
import com.raquo.laminar.api.L.*

object Main {
  sealed abstract class Page {}
  sealed abstract class ToolPage extends Page {}
  case object CompilePage extends ToolPage {}
  case object AsmEmulatePage extends ToolPage {}
  case object BugHuntersPage extends Page {}
  def testSignal(pageSignal: Signal[ToolPage]): Unit = {
    pageSignal
      .splitMatchOne
      .handleValue(CompilePage)(())
      .toSignal
  }
  def main(args: Array[String]): Unit = ()
}

cc @jchyb @sjrd

@jchyb
Copy link
Contributor

jchyb commented Dec 16, 2024

We did reenable exhaustivity checks for 3.5.1 onwards (in #20403, leaving reachability warnings turned off), so some of that back and forth is expected, but it should work for 3.6, so I will investigate that further

@jchyb
Copy link
Contributor

jchyb commented Dec 16, 2024

Minimised:

import scala.quoted._

sealed trait Foo
case object Bar extends Foo
case object Baz extends Foo

object Macro {
  inline def makeMatch() = ${makeMatchImpl}
  def makeMatchImpl(using Quotes) = {
    '{
      (_: Foo) match
        case Bar => ()
    }
  }
}
@main def main() = Macro.makeMatch()

@Gedochao Gedochao removed the stat:needs minimization Needs a self contained minimization label Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:inline area:metaprogramming:other Issues tied to metaprogramming/macros not covered by the other labels. area:reporting Error reporting including formatting, implicit suggestions, etc area:scala.js itype:bug regression This worked in a previous version but doesn't anymore stat:needs bisection Need to use nightly builds and git bisect to find out the commit where this issue was introduced
Projects
None yet
Development

No branches or pull requests

6 participants