Skip to content

Workaround for Scala 3 regression - #178

Open
raquo wants to merge 3 commits into
tulz-app:feature/split-prioritiesfrom
raquo:scala3-bug-fix
Open

Workaround for Scala 3 regression#178
raquo wants to merge 3 commits into
tulz-app:feature/split-prioritiesfrom
raquo:scala3-bug-fix

Conversation

@raquo

@raquo raquo commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Heyo! It turns out that newer Scala versions (verified in 3.8.4, 3.9.0, and 3.10-nightly) have a regression that makes Tuplez 0.5.0-M* throw unwarranted "unreachable case" compiler warnings when using in typical Airstream patterns – simple ones, nothing weird. I haven't noticed until now because 3.3.8 is unaffected.

Scala has a number of unresolved issues about "unreachable case" false positives, I'm not sure which one of them (if any) applies. I'll try to come up with a zero-dependency Scala reproduction later, but I'm out of tokens for now to polish it.

I do have a self-contained reproduction in Tuplez though. If you check out the "Add regression test for scala 3 bug" commit, it's just 0.5.0-M3. Run the test with sbt tuplezScala3Bug/compile, and you'll see it throw warnings on perfectly valid code:

sbt:tuplez> tuplezScala3Bug/compile
[info] compiling 1 Scala source to /Users/raquo/code/scala/tuplez/modules/shared/.jvm/target/scala-3.3.7/classes ...
[info] compiling 1 Scala source to /Users/raquo/code/scala/tuplez/modules/scala3-bug/target/scala-3.9.0/classes ...
[warn] -- [E030] Match case Unreachable Warning: /Users/raquo/code/scala/tuplez/modules/scala3-bug/src/main/scala/app/tulz/tuplez/bug/CompileBugRepro.scala:67:13
[warn] 67 |        case (n, preset, Some(a), Some(b)) => s"$n-$preset-$a-$b"
[warn]    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[warn]    |             Unreachable case
[warn] -- [E121] Pattern Match Warning: /Users/raquo/code/scala/tuplez/modules/scala3-bug/src/main/scala/app/tulz/tuplez/bug/CompileBugRepro.scala:68:13
[warn] 68 |        case _                             => "none"
[warn]    |             ^
[warn]    |Unreachable case except for null (if this is intentional, consider writing case null => instead).
[warn] -- [E030] Match case Unreachable Warning: /Users/raquo/code/scala/tuplez/modules/scala3-bug/src/main/scala/app/tulz/tuplez/bug/CompileBugRepro.scala:75:22
[warn] 75 |      .collect { case (ev, Some(x), Some(y), l, Some(z)) => (ev, x, y, l, z) }
[warn]    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[warn]    |                      Unreachable case
[error] No warnings can be incurred under -Werror
[warn] three warnings found
[error] one error found
[error] (tuplezScala3Bug / Compile / compileIncremental) Compilation failed
[error] Total time: 4 s, completed Sep 4, 2026, 2:56:31 a.m.

In the next commit, I have replaced Tuplez Scala 3 implementation with its Scala 2 implementation. If you run sbt tuplezScala3Bug/compile on that, the test will pass.

The setup of the test as a separate module is weird – that's because Claude determined that we need a compilation boundary to trigger the bug. I'll see if I can bypass that requirement later, but in practice, users of tuplez would be affected either way.

The downside of using Scala 2 implementation is that it's quite a bit more verbose – 4K lines. Claude proposed an alternative Scala 3 implementation that was approx 300 lines – using *: instead of Tuple.Concat, but even though it passed existing tests, I think it wasn't quite finished and required additional testing for edge cases. I haven't submitted that, I don't know if you have time to review all that. The Scala 2 implementation is at least proven.

I'm getting ready to release the next and probably final pre-release version of Laminar v18, and ran into this blocker.

I have verified that this branch fixed the compilation warnings on my codebase. I had 4 warnings in our pretty large codebase. The pattern was as in the test – a tuplez-using Airstream operator like combineWith followed by map or collect with a case .... argument.

If you want to test this yourself, remember that URL-DSL also depends on Tuplez. I checked and its master compiles fine with this branch, although I assume it's not binary compatible, so would need a publishLocal if you use it.

My suggested plan of action is to release this version of Tuplez, and later file a bug with Scala, and bring back your original Scala 3 implementation when the Scala bug is eventually fixed – I assume that'll take a while seeing that they've just released 3.9.0.

Also, please don't bump Tuplez Scala 3 version to 3.9.0 just yet, I haven't confirmed that all Laminar stuff supports it yet.


The "Add sbt-buildkit-dynver plugin" commit is not related to any of this. I just used it to automatically get reasonable dev version (0.5.0-M4-SNAPSHOT) when publishLocal-ing. Feel free to discard that commit or I can remove it if you'd rather not introduce a dependency for it. I'm adding it to all my projects as I got tired of copy-pasting PlayFramework's VersionHelper.scala file into every repo.

@raquo

raquo commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Oh, CI fails because githubWorkflowGenerate added the new bug-repro module's target directory to the list of directories to archive, but the test command does not compile that module (so its target dir does not get created), I made a separate workflow .yml for that. Will sort out later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant