Skip to content

MatchError in macro expansion on AndType from explicit null flow typing. #25208

@johnynek

Description

@johnynek

Compiler version

3.8.1

Minimized code

in one file:

package Foo

import scala.quoted.*

object NonNullFold:

  extension [A](inline arg: A | Null)
    /**
     * Extension method to fold over a nullable value.
     * Uses pattern matching to ensure compatibility with strict equality.
     */
    inline def foldNN[B](inline default: => B)(inline fn: A => B): B =
      ${ foldNNImpl('arg, 'default, 'fn) }

    inline def notNull[B]: Boolean =
      foldNN(false)(_ => true)

  private def foldNNImpl[A: Type, B: Type](
      arg: Expr[A | Null],
      default: Expr[B],
      fn: Expr[A => B]
  )(using Quotes): Expr[B] =
    '{
      // 1. Bind the argument to a val to prevent multiple evaluations
      val t = $arg
      
      // 2. 
      // This works even if 'import scala.language.strictEquality' is active.
      given CanEqual[A | Null, Null] = CanEqual.derived
      if (t == null) $default
      else {
        // CRITICAL FIX: 
        // We define a new local 'val' with the explicit type 'A'.
        // This strips away the complex intersection types (flow typing) from 't'
        // that were causing the MatchError in the unpickler.
        //val safe: A = t.asInstanceOf[A]
        // 3. We cast to A safely because we are in the not-null branch.
        // 4. betaReduce inlines the function body directly here.
       //${ Expr.betaReduce('{ $fn(safe) }) }
        ${ Expr.betaReduce('{ $fn(t) }) }
      }
    }

In another:
import NonNullFold.*

package Foo

val x: String | Null = "not null"

val y = x.foldNN("it's null")(x => x)

Output (click arrow to expand)

Details
error] 135 |val y = x.foldNN("it's null")(x => x)
[error]     |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]     |Exception occurred while executing macro expansion.
[error]     |scala.MatchError: AndType(TermRef(NoPrefix,val t),TypeRef(NoPrefix,type evidence$1$1)) (of class dotty.tools.dotc.core.Types$CachedAndType)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readPathTree$1(TreeUnpickler.scala:1285)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1702)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.$anonfun$50(TreeUnpickler.scala:1767)
[error]     |   at dotty.tools.tasty.TastyReader.until(TastyReader.scala:135)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readHole(TreeUnpickler.scala:1767)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1700)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1531)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler.dotty$tools$dotc$core$tasty$TreeUnpickler$TreeReader$$_$_$$anonfun$34(TreeUnpickler.scala:1521)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readIndexedStats(TreeUnpickler.scala:1248)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readStats(TreeUnpickler.scala:1252)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1521)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1539)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler.dotty$tools$dotc$core$tasty$TreeUnpickler$TreeReader$$_$_$$anonfun$34(TreeUnpickler.scala:1521)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readIndexedStats(TreeUnpickler.scala:1248)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readStats(TreeUnpickler.scala:1252)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1521)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler.dotty$tools$dotc$core$tasty$TreeUnpickler$TreeReader$$_$_$$anonfun$34(TreeUnpickler.scala:1521)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readIndexedStats(TreeUnpickler.scala:1248)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readStats(TreeUnpickler.scala:1252)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1521)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTree$1(TreeUnpickler.scala:1531)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1708)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTree(TreeUnpickler.scala:1273)
[error]     |   at dotty.tools.dotc.core.tasty.TreeUnpickler.unpickle(TreeUnpickler.scala:146)
[error]     |   at dotty.tools.dotc.core.tasty.DottyUnpickler.computeRootTrees(DottyUnpickler.scala:95)
[error]     |   at dotty.tools.dotc.ast.tpd$TreeProvider.rootTrees(tpd.scala:1374)
[error]     |   at dotty.tools.dotc.ast.tpd$TreeProvider.rootTrees$(tpd.scala:1363)
[error]     |   at dotty.tools.dotc.core.tasty.DottyUnpickler.rootTrees(DottyUnpickler.scala:58)
[error]     |   at dotty.tools.dotc.ast.tpd$TreeProvider.tree(tpd.scala:1378)
[error]     |   at dotty.tools.dotc.ast.tpd$TreeProvider.tree$(tpd.scala:1363)
[error]     |   at dotty.tools.dotc.core.tasty.DottyUnpickler.tree(DottyUnpickler.scala:58)
[error]     |   at dotty.tools.dotc.quoted.PickledQuotes$.unpickle(PickledQuotes.scala:278)
[error]     |   at dotty.tools.dotc.quoted.PickledQuotes$.unpickleTerm(PickledQuotes.scala:86)
[error]     |   at scala.quoted.runtime.impl.QuotesImpl.unpickleExprV2(QuotesImpl.scala:3562)
[error]     |   at dev.bosatsu.NonNullFold$.foldNNImpl(NonNullFold.scala:40)
[error]     |   at dev.bosatsu.NonNullFold$.inline$foldNNImpl(NonNullFold.scala:18)
[error]     |
[error]     |---------------------------------------------------------------------------
[error]     |Inline stack trace
[error]     |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]     |This location contains code that was inlined from NonNullFold.scala:13
[error]  13 |      ${ foldNNImpl('arg, 'default, 'fn) }
[error]     |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]      ---------------------------------------------------------------------------
[error] one error found

My work around is to uncomment the safe related variables and cast away the flow typing added by explicit nulls, but I shouldn't have to do that. The compiler shouldn't crash

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions