diff options
author | Eugene Burmako <xeno.by@gmail.com> | 2013-10-02 17:22:07 +0200 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-11-12 18:40:01 -0800 |
commit | 0d5c2f76ea30c6a45471dac635f035e931075453 (patch) | |
tree | 15f477eb256a65f2d260b940f1a3c9bed113a3db | |
parent | 6038bac3513a834e67ab4074c2c7b03aac11b1b3 (diff) | |
download | scala-0d5c2f76ea30c6a45471dac635f035e931075453.tar.gz scala-0d5c2f76ea30c6a45471dac635f035e931075453.tar.bz2 scala-0d5c2f76ea30c6a45471dac635f035e931075453.zip |
blackbox restriction #3: can't affect implicit search
When an application of a blackbox macro is used as an implicit candidate,
no expansion is performed until the macro is selected as the result of
the implicit search.
This makes it impossible to dynamically calculate availability of
implicit macros.
14 files changed, 125 insertions, 64 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index c9dbdb93ff..01acbb8cc2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -596,7 +596,7 @@ trait Implicits { // workaround for deficient context provided by ModelFactoryImplicitSupport#makeImplicitConstraints val isScalaDoc = context.tree == EmptyTree - val itree = atPos(pos.focus) { + val itree0 = atPos(pos.focus) { if (isLocal && !isScalaDoc) { // SI-4270 SI-5376 Always use an unattributed Ident for implicits in the local scope, // rather than an attributed Select, to detect shadowing. @@ -608,15 +608,16 @@ trait Implicits { Select(gen.mkAttributedQualifier(info.pre), implicitMemberName) } } - typingLog("considering", typeDebug.ptTree(itree)) + val itree1 = if (isBlackbox(info.sym)) suppressMacroExpansion(itree0) else itree0 + typingLog("considering", typeDebug.ptTree(itree1)) - def fail(reason: String): SearchResult = failure(itree, reason) - def fallback = typed1(itree, EXPRmode, wildPt) + def fail(reason: String): SearchResult = failure(itree0, reason) + def fallback = typed1(itree1, EXPRmode, wildPt) try { - val itree1 = if (!isView) fallback else pt match { + val itree2 = if (!isView) fallback else pt match { case Function1(arg1, arg2) => typed1( - atPos(itree.pos)(Apply(itree, List(Ident("<argument>") setType approximate(arg1)))), + atPos(itree0.pos)(Apply(itree1, List(Ident("<argument>") setType approximate(arg1)))), EXPRmode, approximate(arg2) ) match { @@ -647,10 +648,10 @@ trait Implicits { if (Statistics.canEnable) Statistics.incCounter(typedImplicits) - val itree2 = if (isView) treeInfo.dissectApplied(itree1).callee - else adapt(itree1, EXPRmode, wildPt) + val itree3 = if (isView) treeInfo.dissectApplied(itree2).callee + else adapt(itree2, EXPRmode, wildPt) - typingStack.showAdapt(itree, itree2, pt, context) + typingStack.showAdapt(itree0, itree3, pt, context) def hasMatchingSymbol(tree: Tree): Boolean = (tree.symbol == info.sym) || { tree match { @@ -663,21 +664,21 @@ trait Implicits { if (context.hasErrors) fail("hasMatchingSymbol reported error: " + context.firstError.get.errMsg) - else if (isLocal && !hasMatchingSymbol(itree1)) + else if (isLocal && !hasMatchingSymbol(itree2)) fail("candidate implicit %s is shadowed by %s".format( - info.sym.fullLocationString, itree1.symbol.fullLocationString)) + info.sym.fullLocationString, itree2.symbol.fullLocationString)) else { val tvars = undetParams map freshVar def ptInstantiated = pt.instantiateTypeParams(undetParams, tvars) - if (matchesPt(itree2.tpe, ptInstantiated, undetParams)) { + if (matchesPt(itree3.tpe, ptInstantiated, undetParams)) { if (tvars.nonEmpty) typingLog("solve", ptLine("tvars" -> tvars, "tvars.constr" -> tvars.map(_.constr))) - val targs = solvedTypes(tvars, undetParams, undetParams map varianceInType(pt), upper = false, lubDepth(itree2.tpe :: pt :: Nil)) + val targs = solvedTypes(tvars, undetParams, undetParams map varianceInType(pt), upper = false, lubDepth(itree3.tpe :: pt :: Nil)) // #2421: check that we correctly instantiated type parameters outside of the implicit tree: - checkBounds(itree2, NoPrefix, NoSymbol, undetParams, targs, "inferred ") + checkBounds(itree3, NoPrefix, NoSymbol, undetParams, targs, "inferred ") context.firstError match { case Some(err) => return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + err.errMsg) @@ -693,7 +694,7 @@ trait Implicits { if (okParams.isEmpty) EmptyTreeTypeSubstituter else { val subst = new TreeTypeSubstituter(okParams, okArgs) - subst traverse itree2 + subst traverse itree3 notifyUndetparamsInferred(okParams, okArgs) subst } @@ -711,9 +712,9 @@ trait Implicits { // This is just called for the side effect of error detection, // see SI-6966 to see what goes wrong if we use the result of this // as the SearchResult. - itree2 match { - case TypeApply(fun, args) => typedTypeApply(itree2, EXPRmode, fun, args) - case Apply(TypeApply(fun, args), _) => typedTypeApply(itree2, EXPRmode, fun, args) // t2421c + itree3 match { + case TypeApply(fun, args) => typedTypeApply(itree3, EXPRmode, fun, args) + case Apply(TypeApply(fun, args), _) => typedTypeApply(itree3, EXPRmode, fun, args) // t2421c case t => t } @@ -721,13 +722,13 @@ trait Implicits { case Some(err) => fail("typing TypeApply reported errors for the implicit tree: " + err.errMsg) case None => - val result = new SearchResult(itree2, subst) + val result = new SearchResult(unsuppressMacroExpansion(itree3), subst) if (Statistics.canEnable) Statistics.incCounter(foundImplicits) typingLog("success", s"inferred value of type $ptInstantiated is $result") result } } - else fail("incompatible: %s does not match expected type %s".format(itree2.tpe, ptInstantiated)) + else fail("incompatible: %s does not match expected type %s".format(itree3.tpe, ptInstantiated)) } } catch { diff --git a/src/reflect/scala/reflect/macros/Enclosures.scala b/src/reflect/scala/reflect/macros/Enclosures.scala index c3d019ccf3..31905c4739 100644 --- a/src/reflect/scala/reflect/macros/Enclosures.scala +++ b/src/reflect/scala/reflect/macros/Enclosures.scala @@ -45,18 +45,6 @@ trait Enclosures { */ def enclosingMacros: List[BlackboxContext] - /** Information about one of the currently considered implicit candidates. - * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, - * hence implicit searches can recursively trigger other implicit searches. - * - * Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion. - * If we're in an implicit macro being expanded, it's included in this list. - * - * Unlike `openImplicits`, this is a val, which means that it gets initialized when the context is created - * and always stays the same regardless of whatever happens during macro expansion. - */ - def enclosingImplicits: List[ImplicitCandidate] - /** Tries to guess a position for the enclosing application. * But that is simple, right? Just dereference `pos` of `macroApplication`? Not really. * If we're in a synthetic macro expansion (no positions), we must do our best to infer the position of something that triggerd this expansion. diff --git a/src/reflect/scala/reflect/macros/Typers.scala b/src/reflect/scala/reflect/macros/Typers.scala index ec90ee8fe6..29c1af110b 100644 --- a/src/reflect/scala/reflect/macros/Typers.scala +++ b/src/reflect/scala/reflect/macros/Typers.scala @@ -23,27 +23,6 @@ trait Typers { */ def openMacros: List[BlackboxContext] - /** Information about one of the currently considered implicit candidates. - * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, - * hence implicit searches can recursively trigger other implicit searches. - * - * `pre` and `sym` provide information about the candidate itself. - * `pt` and `tree` store the parameters of the implicit search the candidate is participating in. - */ - case class ImplicitCandidate(pre: Type, sym: Symbol, pt: Type, tree: Tree) - - /** Information about one of the currently considered implicit candidates. - * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, - * hence implicit searches can recursively trigger other implicit searches. - * - * Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion. - * If we're in an implicit macro being expanded, it's included in this list. - * - * Unlike `enclosingImplicits`, this is a def, which means that it gets recalculated on every invocation, - * so it might change depending on what is going on during macro expansion. - */ - def openImplicits: List[ImplicitCandidate] - /** Typechecks the provided tree against the expected type `pt` in the macro callsite context. * * If `silent` is false, `TypecheckException` will be thrown in case of a typecheck error. diff --git a/src/reflect/scala/reflect/macros/WhiteboxContext.scala b/src/reflect/scala/reflect/macros/WhiteboxContext.scala index 76bc17746c..9d65a5c16e 100644 --- a/src/reflect/scala/reflect/macros/WhiteboxContext.scala +++ b/src/reflect/scala/reflect/macros/WhiteboxContext.scala @@ -40,4 +40,37 @@ trait WhiteboxContext extends BlackboxContext { /** @inheritdoc */ def enclosingMacros: List[WhiteboxContext] + + /** Information about one of the currently considered implicit candidates. + * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, + * hence implicit searches can recursively trigger other implicit searches. + * + * `pre` and `sym` provide information about the candidate itself. + * `pt` and `tree` store the parameters of the implicit search the candidate is participating in. + */ + case class ImplicitCandidate(pre: Type, sym: Symbol, pt: Type, tree: Tree) + + /** Information about one of the currently considered implicit candidates. + * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, + * hence implicit searches can recursively trigger other implicit searches. + * + * Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion. + * If we're in an implicit macro being expanded, it's included in this list. + * + * Unlike `enclosingImplicits`, this is a def, which means that it gets recalculated on every invocation, + * so it might change depending on what is going on during macro expansion. + */ + def openImplicits: List[ImplicitCandidate] + + /** Information about one of the currently considered implicit candidates. + * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, + * hence implicit searches can recursively trigger other implicit searches. + * + * Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion. + * If we're in an implicit macro being expanded, it's included in this list. + * + * Unlike `openImplicits`, this is a val, which means that it gets initialized when the context is created + * and always stays the same regardless of whatever happens during macro expansion. + */ + def enclosingImplicits: List[ImplicitCandidate] }
\ No newline at end of file diff --git a/test/files/neg/macro-blackbox-dynamic-materialization.check b/test/files/neg/macro-blackbox-dynamic-materialization.check new file mode 100644 index 0000000000..f6c73f7edb --- /dev/null +++ b/test/files/neg/macro-blackbox-dynamic-materialization.check @@ -0,0 +1,4 @@ +Test_2.scala:2: error: I don't like classes that contain integers + println(implicitly[Foo[C1]]) + ^ +one error found diff --git a/test/files/neg/macro-blackbox-dynamic-materialization/Macros_1.scala b/test/files/neg/macro-blackbox-dynamic-materialization/Macros_1.scala new file mode 100644 index 0000000000..a00d195005 --- /dev/null +++ b/test/files/neg/macro-blackbox-dynamic-materialization/Macros_1.scala @@ -0,0 +1,25 @@ +import scala.reflect.macros.BlackboxContext +import scala.language.experimental.macros + +trait Foo[T] + +class C1(val x: Int) +class C2(val x: String) + +trait LowPriority { + implicit def lessSpecific[T]: Foo[T] = null +} + +object Foo extends LowPriority { + implicit def moreSpecific[T]: Foo[T] = macro Macros.impl[T] +} + +object Macros { + def impl[T: c.WeakTypeTag](c: BlackboxContext) = { + import c.universe._ + val tpe = weakTypeOf[T] + if (tpe.members.exists(_.typeSignature =:= typeOf[Int])) + c.abort(c.enclosingPosition, "I don't like classes that contain integers") + q"new Foo[$tpe]{ override def toString = ${tpe.toString} }" + } +}
\ No newline at end of file diff --git a/test/files/neg/macro-blackbox-dynamic-materialization/Test_2.scala b/test/files/neg/macro-blackbox-dynamic-materialization/Test_2.scala new file mode 100644 index 0000000000..bf19209ab7 --- /dev/null +++ b/test/files/neg/macro-blackbox-dynamic-materialization/Test_2.scala @@ -0,0 +1,4 @@ +object Test extends App { + println(implicitly[Foo[C1]]) + println(implicitly[Foo[C2]]) +}
\ No newline at end of file diff --git a/test/files/neg/macro-blackbox-fundep-materialization.check b/test/files/neg/macro-blackbox-fundep-materialization.check index a5a9b9a206..3c03064a2d 100644 --- a/test/files/neg/macro-blackbox-fundep-materialization.check +++ b/test/files/neg/macro-blackbox-fundep-materialization.check @@ -1,12 +1,8 @@ -Test_2.scala:7: Iso.materializeIso is not a valid implicit value for Iso[Test.Foo,L] because: -hasMatchingSymbol reported error: type mismatch; +Test_2.scala:7: error: type mismatch; found : Iso[Test.Foo,(Int, String, Boolean)] required: Iso[Test.Foo,Nothing] Note: (Int, String, Boolean) >: Nothing, but trait Iso is invariant in type U. You may wish to define U as -U instead. (SLS 4.5) val equiv = foo(Foo(23, "foo", true)) ^ -Test_2.scala:7: error: could not find implicit value for parameter iso: Iso[Test.Foo,L] - val equiv = foo(Foo(23, "foo", true)) - ^ one error found diff --git a/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala b/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala index 23fcdd6445..9fb374800b 100644 --- a/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala +++ b/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala @@ -1,4 +1,4 @@ -import scala.reflect.macros.BlackboxContext +import scala.reflect.macros.WhiteboxContext import language.experimental.macros trait Complex[T] @@ -6,7 +6,7 @@ trait Complex[T] class Foo(val foo: Foo) object Complex { - def impl[T: c.WeakTypeTag](c: BlackboxContext): c.Expr[Complex[T]] = { + def impl[T: c.WeakTypeTag](c: WhiteboxContext): c.Expr[Complex[T]] = { import c.universe._ val tpe = weakTypeOf[T] for (f <- tpe.declarations.collect{case f: TermSymbol if f.isParamAccessor && !f.isMethod => f}) { diff --git a/test/files/run/macro-sip19-revised/Impls_Macros_1.scala b/test/files/run/macro-sip19-revised/Impls_Macros_1.scala index 870930c7e5..3acc52dbe0 100644 --- a/test/files/run/macro-sip19-revised/Impls_Macros_1.scala +++ b/test/files/run/macro-sip19-revised/Impls_Macros_1.scala @@ -1,7 +1,7 @@ -import scala.reflect.macros.BlackboxContext +import scala.reflect.macros.WhiteboxContext object Macros { - def impl(c: BlackboxContext) = { + def impl(c: WhiteboxContext) = { import c.universe._ val inscope = c.inferImplicitValue(c.mirror.staticClass("SourceLocation").toType) diff --git a/test/files/run/macro-sip19/Impls_Macros_1.scala b/test/files/run/macro-sip19/Impls_Macros_1.scala index 72a3c2568d..f830d2af0d 100644 --- a/test/files/run/macro-sip19/Impls_Macros_1.scala +++ b/test/files/run/macro-sip19/Impls_Macros_1.scala @@ -1,7 +1,7 @@ -import scala.reflect.macros.BlackboxContext +import scala.reflect.macros.WhiteboxContext object Macros { - def impl(c: BlackboxContext) = { + def impl(c: WhiteboxContext) = { import c.universe._ val Apply(fun, args) = c.enclosingImplicits(0).tree val fileName = fun.pos.source.file.file.getName diff --git a/test/files/run/macro-whitebox-dynamic-materialization.check b/test/files/run/macro-whitebox-dynamic-materialization.check new file mode 100644 index 0000000000..ccec8e5b25 --- /dev/null +++ b/test/files/run/macro-whitebox-dynamic-materialization.check @@ -0,0 +1,2 @@ +null +C2 diff --git a/test/files/run/macro-whitebox-dynamic-materialization/Macros_1.scala b/test/files/run/macro-whitebox-dynamic-materialization/Macros_1.scala new file mode 100644 index 0000000000..87cd310b09 --- /dev/null +++ b/test/files/run/macro-whitebox-dynamic-materialization/Macros_1.scala @@ -0,0 +1,25 @@ +import scala.reflect.macros.WhiteboxContext +import scala.language.experimental.macros + +trait Foo[T] + +class C1(val x: Int) +class C2(val x: String) + +trait LowPriority { + implicit def lessSpecific[T]: Foo[T] = null +} + +object Foo extends LowPriority { + implicit def moreSpecific[T]: Foo[T] = macro Macros.impl[T] +} + +object Macros { + def impl[T: c.WeakTypeTag](c: WhiteboxContext) = { + import c.universe._ + val tpe = weakTypeOf[T] + if (tpe.members.exists(_.typeSignature =:= typeOf[Int])) + c.abort(c.enclosingPosition, "I don't like classes that contain integers") + q"new Foo[$tpe]{ override def toString = ${tpe.toString} }" + } +}
\ No newline at end of file diff --git a/test/files/run/macro-whitebox-dynamic-materialization/Test_2.scala b/test/files/run/macro-whitebox-dynamic-materialization/Test_2.scala new file mode 100644 index 0000000000..bf19209ab7 --- /dev/null +++ b/test/files/run/macro-whitebox-dynamic-materialization/Test_2.scala @@ -0,0 +1,4 @@ +object Test extends App { + println(implicitly[Foo[C1]]) + println(implicitly[Foo[C2]]) +}
\ No newline at end of file |