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

Typechecking TypTrees is not idempotent #8123

Closed
scabug opened this issue Jan 7, 2014 · 6 comments
Closed

Typechecking TypTrees is not idempotent #8123

scabug opened this issue Jan 7, 2014 · 6 comments
Labels

Comments

@scabug
Copy link

scabug commented Jan 7, 2014

It is sort of well-known that TypeTrees are very touchy (e.g. see https://github.com/scala/scala/blob/527fd9aea58cf5c1b8f638d0321a8d0947d2916a/src/compiler/scala/reflect/reify/phases/Reshape.scala#L119), but I haven't found any issues exposing that.

@scabug
Copy link
Author

scabug commented Jan 7, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8123?orig=1
Reporter: @xeno-by
Affected Versions: 2.10.3, 2.11.0-M7
Blocks #5464

@scabug
Copy link
Author

scabug commented Jan 7, 2014

@retronym said (edited on Jan 7, 2014 4:09:16 PM UTC):
BTW, I've just been battling the reifier for a typesafe support ticket. Removing inferred types in the hope that they can be recovered later doesn't really work all that well.

Here's what I suggested:

import scala.language.experimental.macros
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.{ universe => ru }
import scala.tools.reflect.ToolBox

class A[T] {
  def foo(lam: T => Any): Unit = macro FooMacros.fooImpl[T]
}
object B {
  def fooTree[T](lam: T => Any, expr: ru.Expr[T => Any], tp: ru.TypeTag[T]): Unit = {
    // no inferred types in the trees
    println(expr)
    expr.tree.asInstanceOf[ru.FunctionApi].vparams.foreach(t => println((t, t.tpe)))

    // Approach 1: make a copy of the Function node with `Trade` as the explicit parameter
    // type
    import ru._
    val tree2 = expr.tree match {
      case Function(param :: Nil, body) =>
        val newTpt = TypeTree(tp.tpe)
        Function(treeCopy.ValDef(param, param.mods, param.name, newTpt, param.rhs) :: Nil, body)
    }
    println(tree2)

    // Approach 2: typecheck the tree with the original expected type of `Trade => Any`.
    import scala.tools.reflect.ToolBox
    val tb = reflect.runtime.currentMirror.mkToolBox()
    implicit val implicitTT = tp
    val typed = tb.typeCheck(expr.tree, pt = typeOf[T => Any])
    println(s"typed: $typed")
  }
}

object FooMacros {
  import scala.reflect.macros.Context

  def fooImpl[T: c.WeakTypeTag](c: Context)(lam: c.Expr[T => Any]): c.Expr[Unit] = {
    import c.{ universe => cu }
    val fExpr = c.Expr[ru.Expr[T => Any]](c.reifyTree(cu.treeBuild.mkRuntimeUniverseRef, cu.EmptyTree, c.typeCheck(lam.tree)))
    // reify (by design) won't carry inferred types to the next meta-level. Instead, we reify the inferred
    // type argument of the macro application, and transport that up a level. This enables us to typecheck
    // at that level and recover the inferred types.
    val fType = c.reifyType(cu.treeBuild.mkRuntimeUniverseRef, cu.EmptyTree, cu.weakTypeOf[T])
    cu.reify { B.fooTree(lam.splice, fExpr.splice, c.Expr[scala.reflect.runtime.universe.TypeTag[T]](fType).splice ) }
  }
}

So that:

This would be able to reconstitute the inferred type:

class Trade(val id: String, val symbol: String, val amount: Int)

object FreeVarTest {

  val c = "xx"

  def main(args: Array[String]): Unit = {
    var freevar = "ms"
    val am = 20
    val a = new A[Trade]
    println("try more")
    a.foo(t => t.id == freevar && (t.amount < am || t.symbol != c))
  }
}

The original code didn't use reifyType, and the reified tree was just (t => ...), which no possibility to re-infer the parameter type.

@scabug
Copy link
Author

scabug commented Jan 7, 2014

@xeno-by said:
Btw, after I've looked at the compiler's code long enough on the plan today, I believe that it should be possible to write a sufficiently robust function that transforms a TypeTree back into a TypTree, even if the original isn't specified.

It wouldn't work in general case, when someone assigned crazy symbols to some parts of a TypeTree using a macro or a compiler plugin, but within the compiler and for well-behaving macros, I believe, it should work fine. I'm going to explore this avenue for #8066.

@scabug
Copy link
Author

scabug commented Jan 7, 2014

@xeno-by said (edited on Jan 7, 2014 4:21:06 PM UTC):
When does removing inferred type parameters fail?

@scabug
Copy link
Author

scabug commented Jan 7, 2014

@retronym said (edited on Jan 7, 2014 5:17:52 PM UTC):
The reified tree contained an empty TypeTree() as the tpt of the parameter. This can only be typechecked if you use the expected type.

@scabug scabug added the typer label Apr 7, 2017
@SethTisue SethTisue added this to the Backlog milestone Mar 2, 2018
@SethTisue
Copy link
Member

consolidating under #5464

@SethTisue SethTisue removed this from the Backlog milestone Mar 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants