From 75b44a6f723762cc3ebc911483beb2aec4cfee78 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 25 Dec 2012 14:09:33 +0100 Subject: [nomaster] macro expansions are now auto-duplicated The fix still requires macro developers to be careful about sharing trees by references, because attributed DefTrees will still bring trouble. However this is an improvement, because it doesn't make matters worse and automatically fixes situations similar to one in the test. A much more thorough discussion with a number of open questions left: http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc Was fixed ages ago in master in one of the paradise backports. Never got to 2.10.x, but it's very useful, so I'm backporting it now. --- bincompat-backward.whitelist.conf | 16 ++++++++++++ bincompat-forward.whitelist.conf | 24 ++++++++++++++++++ .../scala/tools/nsc/typechecker/Macros.scala | 8 +++--- src/reflect/scala/reflect/internal/Trees.scala | 7 ++++-- test/files/run/macro-auto-duplicate.check | 1 + test/files/run/macro-auto-duplicate/Macros_1.scala | 17 +++++++++++++ test/files/run/macro-auto-duplicate/Test_2.scala | 3 +++ test/files/run/macro-duplicate.check | 0 test/files/run/macro-duplicate.flags | 1 + .../files/run/macro-duplicate/Impls_Macros_1.scala | 29 ++++++++++++++++++++++ test/files/run/macro-duplicate/Test_2.scala | 6 +++++ 11 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 test/files/run/macro-auto-duplicate.check create mode 100644 test/files/run/macro-auto-duplicate/Macros_1.scala create mode 100644 test/files/run/macro-auto-duplicate/Test_2.scala create mode 100644 test/files/run/macro-duplicate.check create mode 100644 test/files/run/macro-duplicate.flags create mode 100644 test/files/run/macro-duplicate/Impls_Macros_1.scala create mode 100644 test/files/run/macro-duplicate/Test_2.scala diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index 267631f908..1771ef12bd 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -240,5 +240,21 @@ filter { matchName="scala.reflect.internal.StdAttachments.isMacroExpansionSuppressed" problemName=MissingMethodProblem } + { + matchName="scala.reflect.internal.Trees.scala$reflect$internal$Trees$$duplicator" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Trees.duplicateAndKeepPositions" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Trees.scala$reflect$internal$Trees$$duplicator" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.SymbolTable.scala$reflect$internal$Trees$$duplicator" + problemName=IncompatibleResultTypeProblem + } ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index b3af372b15..1ffc2a0553 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -516,5 +516,29 @@ filter { matchName="scala.reflect.runtime.JavaMirrors#JavaMirror#FromJavaClassCompleter.scala$reflect$runtime$JavaMirrors$JavaMirror$FromJavaClassCompleter$$enterEmptyCtorIfNecessary$1" problemName=MissingMethodProblem } + { + matchName="scala.reflect.internal.Trees$Duplicator" + problemName=MissingClassProblem + }, + { + matchName="scala.reflect.internal.Trees.duplicateAndKeepPositions" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Trees.scala$reflect$internal$Trees$$duplicator" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Trees.scala$reflect$internal$Trees$$duplicator" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.SymbolTable.duplicateAndKeepPositions" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.SymbolTable.scala$reflect$internal$Trees$$duplicator" + problemName=IncompatibleResultTypeProblem + } ] } diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 816f977890..d6ec5f2cb0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -713,9 +713,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { var expectedTpe = expandee.tpe if (isNullaryInvocation(expandee)) expectedTpe = expectedTpe.finalResultType - var typechecked = typecheck("macro def return type", expanded, expectedTpe) - typechecked = typecheck("expected type", typechecked, pt) - typechecked + // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc + val expanded0 = duplicateAndKeepPositions(expanded) + val expanded1 = typecheck("macro def return type", expanded0, expectedTpe) + val expanded2 = typecheck("expected type", expanded1, pt) + expanded2 } finally { popMacroContext() } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 2585b541ed..53b9b1d88e 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1526,15 +1526,18 @@ trait Trees extends api.Trees { self: SymbolTable => } } - private lazy val duplicator = new Transformer { + private lazy val duplicator = new Duplicator(focusPositions = true) + private class Duplicator(focusPositions: Boolean) extends Transformer { override val treeCopy = newStrictTreeCopier override def transform(t: Tree) = { val t1 = super.transform(t) - if ((t1 ne t) && t1.pos.isRange) t1 setPos t.pos.focus + if ((t1 ne t) && t1.pos.isRange && focusPositions) t1 setPos t.pos.focus t1 } } + def duplicateAndKeepPositions(tree: Tree) = new Duplicator(focusPositions = false) transform tree + // ------ copiers ------------------------------------------- def copyDefDef(tree: Tree)( diff --git a/test/files/run/macro-auto-duplicate.check b/test/files/run/macro-auto-duplicate.check new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/test/files/run/macro-auto-duplicate.check @@ -0,0 +1 @@ +42 diff --git a/test/files/run/macro-auto-duplicate/Macros_1.scala b/test/files/run/macro-auto-duplicate/Macros_1.scala new file mode 100644 index 0000000000..e3df05ba50 --- /dev/null +++ b/test/files/run/macro-auto-duplicate/Macros_1.scala @@ -0,0 +1,17 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +object Macros { + def impl(c: Context) = { + import c.universe._ + val x = Ident(newTermName("x")) + def defAndUseX(rhs: Tree) = { + Block(List(ValDef(NoMods, newTermName("x"), TypeTree(), rhs)), x) + } + val xi4 = defAndUseX(Literal(Constant(4))) + val xs2 = defAndUseX(Literal(Constant("2"))) + c.Expr[String](Apply(Select(xi4, newTermName("$plus")), List(xs2))) + } + + def foo = macro impl +} \ No newline at end of file diff --git a/test/files/run/macro-auto-duplicate/Test_2.scala b/test/files/run/macro-auto-duplicate/Test_2.scala new file mode 100644 index 0000000000..f697da6020 --- /dev/null +++ b/test/files/run/macro-auto-duplicate/Test_2.scala @@ -0,0 +1,3 @@ +object Test extends App { + println(Macros.foo) +} \ No newline at end of file diff --git a/test/files/run/macro-duplicate.check b/test/files/run/macro-duplicate.check new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/files/run/macro-duplicate.flags b/test/files/run/macro-duplicate.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/run/macro-duplicate.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/run/macro-duplicate/Impls_Macros_1.scala b/test/files/run/macro-duplicate/Impls_Macros_1.scala new file mode 100644 index 0000000000..de81923330 --- /dev/null +++ b/test/files/run/macro-duplicate/Impls_Macros_1.scala @@ -0,0 +1,29 @@ +import scala.reflect.macros.Context + +object Macros { + def impl(c: Context) = { + import c.universe._ + val Expr(Block((cdef: ClassDef) :: Nil, _)) = reify { class C { def x = 2 } } + val cdef1 = + new Transformer { + override def transform(tree: Tree): Tree = tree match { + case Template(_, _, ctor :: defs) => + val defs1 = defs collect { + case ddef @ DefDef(mods, name, tparams, vparamss, tpt, body) => + val future = Select(Select(Select(Ident(newTermName("scala")), newTermName("concurrent")), newTermName("package")), newTermName("future")) + val Future = Select(Select(Ident(newTermName("scala")), newTermName("concurrent")), newTypeName("Future")) + val tpt1 = if (tpt.isEmpty) tpt else AppliedTypeTree(Future, List(tpt)) + val body1 = Apply(future, List(body)) + val name1 = newTermName("async" + name.toString.capitalize) + DefDef(mods, name1, tparams, vparamss, tpt1, body1) + } + Template(Nil, emptyValDef, ctor +: defs ::: defs1) + case _ => + super.transform(tree) + } + } transform cdef + c.Expr[Unit](Block(cdef1 :: Nil, Literal(Constant(())))) + } + + def foo = macro impl +} \ No newline at end of file diff --git a/test/files/run/macro-duplicate/Test_2.scala b/test/files/run/macro-duplicate/Test_2.scala new file mode 100644 index 0000000000..6dbd4382d3 --- /dev/null +++ b/test/files/run/macro-duplicate/Test_2.scala @@ -0,0 +1,6 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global + +object Test extends App { + Macros.foo +} \ No newline at end of file -- cgit v1.2.3