scala.reflect.reify.codegen.GenTrees.scala Maven / Gradle / Ivy
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.reflect.reify
package codegen
trait GenTrees {
self: Reifier =>
import global._
import definitions._
// unfortunately, these are necessary to reify AnnotatedTypes
// I'd gladly get rid of them, but I don't fancy making a metaprogramming API that doesn't work with annotated types
// luckily for our confidence, these vars are mutated only within a very restricted code execution path
def reifyTreeSymbols: Boolean = state.reifyTreeSymbols
def reifyTreeTypes: Boolean = state.reifyTreeTypes
/**
* Reify a tree.
* For internal use only, use `reified` instead.
*/
def reifyTree(tree: Tree): Tree = {
assert(tree != null, "tree is null")
if (tree.isErroneous)
CannotReifyErroneousReifee(tree)
val splicedTree = spliceTree(tree)
if (splicedTree != EmptyTree)
return splicedTree
// the idea behind the new reincarnation of reifier is a simple maxim:
//
// never call `reifyType` to reify a tree
//
// this works because the stuff we are reifying was once represented with trees only
// and lexical scope information can be fully captured by reifying symbols
//
// to enable this idyll, we work hard in the `Reshape` phase
// which replaces all types with equivalent trees and works around non-idempotencies of the typechecker
//
// why bother? because this brings method to the madness
// the first prototype of reification reified all types and symbols for all trees => this quickly became unwieldy
// the second prototype reified external types, but avoided reifying ones local to the reifee => this created an ugly irregularity
// current approach is uniform and compact
var rtree: Tree = tree match {
case FreeDef(_, _, _, _, _) => reifyNestedFreeDef(tree)
case FreeRef(_, _) => reifyNestedFreeRef(tree)
case BoundTerm(tree) => reifyBoundTerm(tree)
case BoundType(tree) => reifyBoundType(tree)
case _ => reifyTreeSyntactically(tree)
}
// usually we don't reify symbols/types, because they can be re-inferred during subsequent reflective compilation
// however, reification of AnnotatedTypes is special. see `reifyType` to find out why.
if (reifyTreeSymbols && tree.hasSymbolField) {
if (reifyDebug) println("reifying symbol %s for tree %s".format(tree.symbol, tree))
rtree = mirrorBuildCall(nme.setSymbol, rtree, reify(tree.symbol))
}
if (reifyTreeTypes && tree.tpe != null) {
if (reifyDebug) println("reifying type %s for tree %s".format(tree.tpe, tree))
rtree = mirrorBuildCall(nme.setType, rtree, reify(tree.tpe))
}
rtree
}
def reifyTreeSyntactically(tree: Tree): Tree = tree match {
case global.EmptyTree => reifyMirrorObject(EmptyTree)
case global.noSelfType => mirrorSelect(nme.noSelfType)
case global.pendingSuperCall => mirrorSelect(nme.pendingSuperCall)
case Literal(const @ Constant(_)) => mirrorCall(nme.Literal, reifyProduct(const))
case Import(expr, selectors) => mirrorCall(nme.Import, reify(expr), mkList(selectors map reifyProduct))
case _ => reifyProduct(tree)
}
def reifyFlags(flags: FlagSet) =
if (flags != 0) reifyBuildCall(nme.FlagsRepr, flags) else mirrorSelect(nme.NoFlags)
def reifyModifiers(m: global.Modifiers) =
if (m == NoMods) mirrorSelect(nme.NoMods)
else mirrorFactoryCall(nme.Modifiers, reifyFlags(m.flags), reify(m.privateWithin), reify(m.annotations))
private def spliceTree(tree: Tree): Tree = {
tree match {
case TreeSplice(splicee) =>
if (reifyDebug) println("splicing " + tree)
// see `Metalevels` for more info about metalevel breaches
// and about how we deal with splices that contain them
val isMetalevelBreach = splicee exists (sub => sub.hasSymbolField && sub.symbol != NoSymbol && sub.symbol.metalevel > 0)
val isRuntimeEval = splicee exists (sub => sub.hasSymbolField && sub.symbol == ExprSplice)
if (isMetalevelBreach || isRuntimeEval) {
// we used to convert dynamic splices into runtime evals transparently, but we no longer do that
// why? see comments in `Metalevels`
// if (reifyDebug) println("splicing has failed: cannot splice when facing a metalevel breach")
// EmptyTree
CannotReifyRuntimeSplice(tree)
} else {
if (reifyDebug) println("splicing has succeeded")
splicee match {
// we intentionally don't care about the prefix (the first underscore in the `ReifiedTree` pattern match)
case ReifiedTree(_, _, inlinedSymtab, rtree, _, _, _) =>
if (reifyDebug) println("inlining the splicee")
// all free vars local to the enclosing reifee should've already been inlined by `Metalevels`
for (sym <- inlinedSymtab.syms if sym.isLocalToReifee)
abort("free var local to the reifee, should have already been inlined by Metalevels: " + inlinedSymtab.symDef(sym))
state.symtab ++= inlinedSymtab
rtree
case _ =>
val migrated = Apply(Select(splicee, nme.in), List(Ident(nme.MIRROR_SHORT)))
Select(migrated, nme.tree)
}
}
case _ =>
EmptyTree
}
}
// unlike in `reifyBoundType` we can skip checking for `tpe` being local or not local w.r.t the reifee
// a single check for a symbol of the bound term should be enough
// that's because only Idents and Thises can be bound terms, and they cannot host complex types
private def reifyBoundTerm(tree: Tree): Tree = {
val sym = tree.symbol
tree match {
case This(qual) =>
assert(sym != NoSymbol, "unexpected: bound term that doesn't have a symbol: " + showRaw(tree))
if (sym.isLocalToReifee)
mirrorCall(nme.This, reify(qual))
else if (sym.isClass && !sym.isModuleClass) {
if (reifyDebug) println("This for %s, reified as freeVar".format(sym))
if (reifyDebug) println("Free: " + sym)
mirrorBuildCall(nme.mkIdent, reifyFreeTerm(This(sym)))
}
else {
if (reifyDebug) println("This for %s, reified as This".format(sym))
mirrorBuildCall(nme.mkThis, reify(sym))
}
case Ident(name) =>
if (sym == NoSymbol) {
// this sometimes happens, e.g. for binds that don't have a body
// or for untyped code generated during previous phases
// (see a comment in Reifiers about the latter, starting with "why do we reset attrs?")
mirrorCall(nme.Ident, reify(name))
}
else if (!sym.isLocalToReifee) {
if (sym.isVariable && sym.owner.isTerm) {
captureVariable(sym) // Note order dependency: captureVariable needs to come before reification here.
mirrorCall(nme.Select, mirrorBuildCall(nme.mkIdent, reify(sym)), reify(nme.elem))
}
else mirrorBuildCall(nme.mkIdent, reify(sym))
}
else mirrorCall(nme.Ident, reify(name))
case Select(qual, name) =>
if (qual.symbol != null && qual.symbol.hasPackageFlag) {
mirrorBuildCall(nme.mkIdent, reify(sym))
} else {
val effectiveName = if (sym != null && sym != NoSymbol) sym.name else name
reifyProduct(Select(qual, effectiveName))
}
case _ =>
throw new Error("internal error: %s (%s, %s) is not supported".format(tree, tree.productPrefix, tree.getClass))
}
}
private def reifyBoundType(tree: RefTree): Tree = {
val sym = tree.symbol
val tpe = tree.tpe
def reifyBoundType(tree: RefTree): Tree = {
assert(tpe != null, "unexpected: bound type that doesn't have a tpe: " + showRaw(tree))
// if a symbol or a type of the scrutinee are local to reifee
// (e.g. point to a locally declared class or to a path-dependent thingie that depends on a variable defined within the reifee)
// then we can reify the scrutinee as a symless AST and that will definitely be hygienic
// why? because then typechecking of a scrutinee doesn't depend on the environment external to the quasiquote
// otherwise we need to reify the corresponding type
if (sym.isLocalToReifee || tpe.isLocalToReifee || treeInfo.isWildcardStarType(tree))
reifyProduct(tree)
else {
if (reifyDebug) println("reifying bound type %s (underlying type is %s)".format(sym, tpe))
if (tpe.isSpliceable) {
val spliced = spliceType(tpe)
if (spliced == EmptyTree) {
if (reifyDebug) println("splicing failed: reify as is")
mirrorBuildCall(nme.mkTypeTree, reify(tpe))
}
else spliced match {
case TypeRefToFreeType(freeType) =>
if (reifyDebug) println("splicing returned a free type: " + freeType)
Ident(freeType)
case _ =>
if (reifyDebug) println("splicing succeeded: " + spliced)
mirrorBuildCall(nme.mkTypeTree, spliced)
}
}
else tree match {
case Select(qual, name) if !qual.symbol.hasPackageFlag =>
if (reifyDebug) println(s"reifying Select($qual, $name)")
mirrorCall(nme.Select, reify(qual), reify(name))
case SelectFromTypeTree(qual, name) =>
if (reifyDebug) println(s"reifying SelectFromTypeTree($qual, $name)")
mirrorCall(nme.SelectFromTypeTree, reify(qual), reify(name))
case _ if sym.isLocatable =>
if (reifyDebug) println(s"tpe is locatable: reify as Ident($sym)")
mirrorBuildCall(nme.mkIdent, reify(sym))
case _ =>
if (reifyDebug) println(s"tpe is not locatable: reify as TypeTree($tpe)")
mirrorBuildCall(nme.mkTypeTree, reify(tpe))
}
}
}
tree match {
case Select(qual, name) if name != sym.name =>
reifyBoundType(Select(qual, sym.name))
case Select(_, _) | SelectFromTypeTree(_, _) | Ident(_) =>
reifyBoundType(tree)
case _ =>
throw new Error("internal error: %s (%s, %s) is not supported".format(tree, tree.productPrefix, tree.getClass))
}
}
private def reifyNestedFreeDef(tree: Tree): Tree = {
if (reifyDebug) println("nested free def: %s".format(showRaw(tree)))
reifyProduct(tree)
}
private def reifyNestedFreeRef(tree: Tree): Tree = {
if (reifyDebug) println("nested free ref: %s".format(showRaw(tree)))
reifyProduct(tree)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy