Scala Programming Language
  1. Scala Programming Language
  2. SI-6187

Typechecking partial functions is not idempotent

    Details

      Description

      The following macro definition and usage shows a problematic interaction between resetLocalAttrs and partial functions.

      Usage:

      def m[T](t: T): List[List[T]] =
        demo( List((t,true)) collect { case (x,true) => x } )
      

      Definition:

      def demo[T](t: T): List[T] = macro macroImpl[T]
      
      def macroImpl[T: c.AbsTypeTag](c: Context)(t: c.Expr[T]): c.Expr[List[T]] =
      {
        val r = c.universe.reify { List(t.splice) }
        c.Expr[List[T]]( c.resetLocalAttrs(r.tree) )
      }
      

      The error is:

      class Any is abstract; cannot be instantiated
           demo( List((t,true)) collect { case (x,true) => x })
                                        ^
      

      The tree before and after calling resetLocalAttrs:

      Before:

       immutable.this.List.apply(immutable.this.List.apply[(T, Boolean)](scala.Tuple2.apply[T, Boolean](t, true)).collect[T, List[T]]({
        @SerialVersionUID(0) final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[(T, Boolean),T] with Serializable {
          def <init>(): anonymous class $anonfun = {
            $anonfun.super.<init>();
            ()
          };
          final override def applyOrElse[A1 >: Nothing <: (T, Boolean), B1 >: T <: Any](x$1: A1, default: A1 => B1): B1 = (x$1: A1 @unchecked) match {
            case (_1: T, _2: Boolean)(T, Boolean)((x @ _), true) => x
          };
          final def isDefinedAt(x$1: (T, Boolean)): Boolean = (x$1: (T, Boolean) @unchecked) match {
            case (_1: T, _2: Boolean)(T, Boolean)((x @ _), true) => true
          }
        };
        new anonymous class $anonfun()
      })(immutable.this.List.canBuildFrom[T]))
      

      After:

       immutable.this.List.apply(immutable.this.List.apply[(T, Boolean)](scala.Tuple2.apply[T, Boolean](t, true)).collect[T, List[T]]({
        final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[(T, Boolean),T] with Serializable {
          def <init>() = {
            super.<init>();
            ()
          };
          final override def applyOrElse[A1 <: (T, Boolean), B1 >: T](x$1, default) = (x$1: <type ?>) match {
            case (_1: T, _2: Boolean)(T, Boolean)((x @ _), true) => x
          };
          final def isDefinedAt(x$1: (T, Boolean)): Boolean = (x$1: (T, Boolean) @unchecked) match {
            case (_1: T, _2: Boolean)(T, Boolean)((x @ _), true) => true
          }
        };
        new <type ?>()
      })(immutable.this.List.canBuildFrom[T])) 
      

        Issue Links

          Activity

          Hide
          Jason Zaugg added a comment -

          A potential workaround, for those who would like to get something working with 2.10.0, is to undo the translation of pattern matching anonymous partial functions.

          Care must be taken in the other parts of the macro to then treat Match(EmptyTree, cases) as such a contruct, rather than a regular pattern match. If you weren't doing that anyway, your macro might not work under -Xoldpatmat.

            private object RestorePatternMatchingFunctions extends Transformer {
          
              override def transform(tree: Tree): Tree = {
                val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet]
                def isSynthetic(cd: ClassDef) = cd.mods hasFlag SYNTHETIC
                val ApplyOrElse = newTermName("applyOrElse")
          
                tree match {
                  case Block(
                  (cd@ClassDef(_, _, _, Template(_, _, body))) :: Nil,
                  Apply(Select(New(a), nme.CONSTRUCTOR), Nil)) if isSynthetic(cd) =>
                    val restored = (body collectFirst {
                      case DefDef(_, ApplyOrElse, _, _, _, Match(_, cases)) =>
                        val transformedCases = super.transformStats(cases, currentOwner).asInstanceOf[List[CaseDef]]
                        Match(EmptyTree, transformedCases)
                    }).getOrElse(c.abort(tree.pos, s"Internal Error: Unable to find original pattern matching cases in: $body"))
                    restored
                  case t                                                          => super.transform(t)
                }
              }
            }
          
          Show
          Jason Zaugg added a comment - A potential workaround, for those who would like to get something working with 2.10.0, is to undo the translation of pattern matching anonymous partial functions. Care must be taken in the other parts of the macro to then treat Match(EmptyTree, cases) as such a contruct, rather than a regular pattern match. If you weren't doing that anyway, your macro might not work under -Xoldpatmat . private object RestorePatternMatchingFunctions extends Transformer { override def transform(tree: Tree): Tree = { val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet] def isSynthetic(cd: ClassDef) = cd.mods hasFlag SYNTHETIC val ApplyOrElse = newTermName("applyOrElse") tree match { case Block( (cd@ClassDef(_, _, _, Template(_, _, body))) :: Nil, Apply(Select(New(a), nme.CONSTRUCTOR), Nil)) if isSynthetic(cd) => val restored = (body collectFirst { case DefDef(_, ApplyOrElse, _, _, _, Match(_, cases)) => val transformedCases = super.transformStats(cases, currentOwner).asInstanceOf[List[CaseDef]] Match(EmptyTree, transformedCases) }).getOrElse(c.abort(tree.pos, s"Internal Error: Unable to find original pattern matching cases in: $body")) restored case t => super.transform(t) } } }
          Hide
          Jason Zaugg added a comment - - edited

          Let's try for some sort of compiler implemented workaround for 2.10.1.

          Show
          Jason Zaugg added a comment - - edited Let's try for some sort of compiler implemented workaround for 2.10.1.
          Hide
          Jason Zaugg added a comment -

          I've cobbled something together to get rid of `DefaultOverrideMatchAttachment`.

          https://github.com/retronym/scala/compare/scala:2.10.x...retronym:ticket/6187

          Now I need to figure out what parts of the patch are essential for the fix; I'll admit to a little cargo cult along the way.

          Show
          Jason Zaugg added a comment - I've cobbled something together to get rid of `DefaultOverrideMatchAttachment`. https://github.com/retronym/scala/compare/scala:2.10.x...retronym:ticket/6187 Now I need to figure out what parts of the patch are essential for the fix; I'll admit to a little cargo cult along the way.
          Show
          Jason Zaugg added a comment - https://github.com/scala/scala/pull/2011
          Hide
          James Iry added a comment -
          Show
          James Iry added a comment - superseded by https://github.com/scala/scala/pull/2090

            People

            • Assignee:
              Jason Zaugg
              Reporter:
              Mark Harrah
            • Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development