Skip to content

Commit 23b69c1

Browse files
committed
Merge pull request scala#2094 from scalamacros/ticket/6591
SI-6591 Reify and path-dependent types
2 parents 6537d79 + 57c0e63 commit 23b69c1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+309
-105
lines changed

src/compiler/scala/reflect/reify/codegen/GenSymbols.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package scala.reflect.reify
22
package codegen
33

4+
import scala.reflect.internal.Flags._
5+
46
trait GenSymbols {
57
self: Reifier =>
68

@@ -100,6 +102,33 @@ trait GenSymbols {
100102
reifyIntoSymtab(binding.symbol) { sym =>
101103
if (reifyDebug) println("Free term" + (if (sym.isCapturedVariable) " (captured)" else "") + ": " + sym + "(" + sym.accurateKindString + ")")
102104
val name = newTermName(nme.REIFY_FREE_PREFIX + sym.name + (if (sym.isType) nme.REIFY_FREE_THIS_SUFFIX else ""))
105+
// We need to note whether the free value being reified is stable or not to guide subsequent reflective compilation.
106+
// Here's why reflection compilation needs our help.
107+
//
108+
// When dealing with a tree, which contain free values, toolboxes extract those and wrap the entire tree in a Function
109+
// having parameters defined for every free values in the tree. For example, evaluating
110+
//
111+
// Ident(setTypeSignature(newFreeTerm("x", 2), <Int>))
112+
//
113+
// Will generate something like
114+
//
115+
// object wrapper {
116+
// def wrapper(x: () => Int) = {
117+
// x()
118+
// }
119+
// }
120+
//
121+
// Note that free values get transformed into, effectively, by-name parameters. This is done to make sure
122+
// that evaluation order is kept intact. And indeed, we cannot just evaluate all free values at once in order
123+
// to obtain arguments for wrapper.wrapper, because if some of the free values end up being unused during evaluation,
124+
// we might end up doing unnecessary calculations.
125+
//
126+
// So far, so good - we didn't need any flags at all. However, if the code being reified contains path-dependent types,
127+
// we're in trouble, because valid code like `free.T` ends up being transformed into `free.apply().T`, which won't compile.
128+
//
129+
// To overcome this glitch, we note whether a given free term is stable or not (because vars can also end up being free terms).
130+
// Then, if a free term is stable, we tell the compiler to treat `free.apply()` specially and assume that it's stable.
131+
if (!sym.isMutable) sym setFlag STABLE
103132
if (sym.isCapturedVariable) {
104133
assert(binding.isInstanceOf[Ident], showRaw(binding))
105134
val capturedBinding = referenceCapturedVariable(sym)

src/compiler/scala/reflect/reify/codegen/GenTrees.scala

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,21 +153,23 @@ trait GenTrees {
153153
else mirrorCall(nme.Ident, reify(name))
154154

155155
case Select(qual, name) =>
156-
if (sym == NoSymbol || sym.name == name)
157-
reifyProduct(tree)
158-
else
159-
reifyProduct(Select(qual, sym.name))
156+
if (qual.symbol != null && qual.symbol.isPackage) {
157+
mirrorBuildCall(nme.Ident, reify(sym))
158+
} else {
159+
val effectiveName = if (sym != null && sym != NoSymbol) sym.name else name
160+
reifyProduct(Select(qual, effectiveName))
161+
}
160162

161163
case _ =>
162164
throw new Error("internal error: %s (%s, %s) is not supported".format(tree, tree.productPrefix, tree.getClass))
163165
}
164166
}
165167

166-
private def reifyBoundType(tree: Tree): Tree = {
168+
private def reifyBoundType(tree: RefTree): Tree = {
167169
val sym = tree.symbol
168170
val tpe = tree.tpe
169171

170-
def reifyBoundType(tree: Tree): Tree = {
172+
def reifyBoundType(tree: RefTree): Tree = {
171173
assert(tpe != null, "unexpected: bound type that doesn't have a tpe: " + showRaw(tree))
172174

173175
// if a symbol or a type of the scrutinee are local to reifee
@@ -196,13 +198,19 @@ trait GenTrees {
196198
mirrorBuildCall(nme.TypeTree, spliced)
197199
}
198200
}
199-
else if (sym.isLocatable) {
200-
if (reifyDebug) println("tpe is locatable: reify as Ident(%s)".format(sym))
201-
mirrorBuildCall(nme.Ident, reify(sym))
202-
}
203-
else {
204-
if (reifyDebug) println("tpe is not locatable: reify as TypeTree(%s)".format(tpe))
205-
mirrorBuildCall(nme.TypeTree, reify(tpe))
201+
else tree match {
202+
case Select(qual, name) if !qual.symbol.isPackage =>
203+
if (reifyDebug) println(s"reifying Select($qual, $name)")
204+
mirrorCall(nme.Select, reify(qual), reify(name))
205+
case SelectFromTypeTree(qual, name) =>
206+
if (reifyDebug) println(s"reifying SelectFromTypeTree($qual, $name)")
207+
mirrorCall(nme.SelectFromTypeTree, reify(qual), reify(name))
208+
case _ if sym.isLocatable =>
209+
if (reifyDebug) println(s"tpe is locatable: reify as Ident($sym)")
210+
mirrorBuildCall(nme.Ident, reify(sym))
211+
case _ =>
212+
if (reifyDebug) println(s"tpe is not locatable: reify as TypeTree($tpe)")
213+
mirrorBuildCall(nme.TypeTree, reify(tpe))
206214
}
207215
}
208216
}

src/compiler/scala/reflect/reify/utils/Extractors.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,12 @@ trait Extractors {
263263
}
264264

265265
object BoundType {
266-
def unapply(tree: Tree): Option[Tree] = tree match {
267-
case Select(_, name) if name.isTypeName =>
266+
def unapply(tree: Tree): Option[RefTree] = tree match {
267+
case tree @ Select(_, name) if name.isTypeName =>
268268
Some(tree)
269-
case SelectFromTypeTree(_, name) if name.isTypeName =>
269+
case tree @ SelectFromTypeTree(_, _) =>
270270
Some(tree)
271-
case Ident(name) if name.isTypeName =>
271+
case tree @ Ident(name) if name.isTypeName =>
272272
Some(tree)
273273
case _ =>
274274
None

src/compiler/scala/tools/reflect/ToolBoxFactory.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import scala.tools.nsc.typechecker.Modes
88
import scala.tools.nsc.io.VirtualDirectory
99
import scala.tools.nsc.interpreter.AbstractFileClassLoader
1010
import scala.tools.nsc.util.FreshNameCreator
11-
import scala.reflect.internal.Flags
11+
import scala.reflect.internal.Flags._
1212
import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, NoFile}
1313
import java.lang.{Class => jClass}
1414
import scala.compat.Platform.EOL
@@ -211,8 +211,9 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
211211

212212
val meth = obj.moduleClass.newMethod(newTermName(wrapperMethodName))
213213
def makeParam(schema: (FreeTermSymbol, TermName)) = {
214+
// see a detailed explanation of the STABLE trick in `GenSymbols.reifyFreeTerm`
214215
val (fv, name) = schema
215-
meth.newValueParameter(name) setInfo appliedType(definitions.FunctionClass(0).tpe, List(fv.tpe.resultType))
216+
meth.newValueParameter(name, newFlags = if (fv.hasStableFlag) STABLE else 0) setInfo appliedType(definitions.FunctionClass(0).tpe, List(fv.tpe.resultType))
216217
}
217218
meth setInfo MethodType(freeTerms.map(makeParam).toList, AnyClass.tpe)
218219
minfo.decls enter meth
@@ -418,4 +419,3 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
418419
def eval(tree: u.Tree): Any = compile(tree)()
419420
}
420421
}
421-

src/reflect/scala/reflect/internal/Flags.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class Flags extends ModifierFlags {
274274
* from Modifiers. Others which may be applied at creation time are:
275275
* SYNTHETIC.
276276
*/
277-
final val ValueParameterFlags = BYNAMEPARAM | IMPLICIT | DEFAULTPARAM
277+
final val ValueParameterFlags = BYNAMEPARAM | IMPLICIT | DEFAULTPARAM | STABLE
278278
final val BeanPropertyFlags = DEFERRED | OVERRIDE | STATIC
279279
final val VarianceFlags = COVARIANT | CONTRAVARIANT
280280

src/reflect/scala/reflect/internal/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ trait StdNames {
607607
val RootPackage: NameType = "RootPackage"
608608
val RootClass: NameType = "RootClass"
609609
val Select: NameType = "Select"
610+
val SelectFromTypeTree: NameType = "SelectFromTypeTree"
610611
val StringContext: NameType = "StringContext"
611612
val This: NameType = "This"
612613
val ThisType: NameType = "ThisType"

src/reflect/scala/reflect/internal/TreeInfo.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,15 @@ abstract class TreeInfo {
8989
tree.symbol.isStable && isExprSafeToInline(qual)
9090
case TypeApply(fn, _) =>
9191
isExprSafeToInline(fn)
92+
case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX =>
93+
// see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm`
94+
free.symbol.hasStableFlag && isExprSafeToInline(free)
9295
case Apply(fn, List()) =>
93-
/* Note: After uncurry, field accesses are represented as Apply(getter, Nil),
94-
* so an Apply can also be pure.
95-
* However, before typing, applications of nullary functional values are also
96-
* Apply(function, Nil) trees. To prevent them from being treated as pure,
97-
* we check that the callee is a method. */
96+
// Note: After uncurry, field accesses are represented as Apply(getter, Nil),
97+
// so an Apply can also be pure.
98+
// However, before typing, applications of nullary functional values are also
99+
// Apply(function, Nil) trees. To prevent them from being treated as pure,
100+
// we check that the callee is a method.
98101
fn.symbol.isMethod && !fn.symbol.isLazy && isExprSafeToInline(fn)
99102
case Typed(expr, _) =>
100103
isExprSafeToInline(expr)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
List[Int]
1+
scala.List[Int]

test/files/run/idempotency-case-classes.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ C(2,3)
5050
Some.apply[(Int, Int)](Tuple2.apply[Int, Int](x$0.x, x$0.y));
5151
<synthetic> private def readResolve(): Object = C
5252
};
53-
scala.this.Predef.println(C.apply(2, 3))
53+
Predef.println(C.apply(2, 3))
5454
}
5555
error!

test/files/run/idempotency-lazy-vals.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
};
1919
val c: C = new C();
2020
import c._;
21-
c.x.*(scala.this.Predef.implicitly[Int](c.y))
21+
c.x.*(Predef.implicitly[Int](c.y))
2222
}
2323
error!

0 commit comments

Comments
 (0)