Skip to content

Commit a16c3ee

Browse files
committed
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.
1 parent 096bc95 commit a16c3ee

File tree

14 files changed

+125
-64
lines changed

14 files changed

+125
-64
lines changed

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ trait Implicits {
596596
// workaround for deficient context provided by ModelFactoryImplicitSupport#makeImplicitConstraints
597597
val isScalaDoc = context.tree == EmptyTree
598598

599-
val itree = atPos(pos.focus) {
599+
val itree0 = atPos(pos.focus) {
600600
if (isLocal && !isScalaDoc) {
601601
// SI-4270 SI-5376 Always use an unattributed Ident for implicits in the local scope,
602602
// rather than an attributed Select, to detect shadowing.
@@ -608,15 +608,16 @@ trait Implicits {
608608
Select(gen.mkAttributedQualifier(info.pre), implicitMemberName)
609609
}
610610
}
611-
typingLog("considering", typeDebug.ptTree(itree))
611+
val itree1 = if (isBlackbox(info.sym)) suppressMacroExpansion(itree0) else itree0
612+
typingLog("considering", typeDebug.ptTree(itree1))
612613

613-
def fail(reason: String): SearchResult = failure(itree, reason)
614-
def fallback = typed1(itree, EXPRmode, wildPt)
614+
def fail(reason: String): SearchResult = failure(itree0, reason)
615+
def fallback = typed1(itree1, EXPRmode, wildPt)
615616
try {
616-
val itree1 = if (!isView) fallback else pt match {
617+
val itree2 = if (!isView) fallback else pt match {
617618
case Function1(arg1, arg2) =>
618619
typed1(
619-
atPos(itree.pos)(Apply(itree, List(Ident("<argument>") setType approximate(arg1)))),
620+
atPos(itree0.pos)(Apply(itree1, List(Ident("<argument>") setType approximate(arg1)))),
620621
EXPRmode,
621622
approximate(arg2)
622623
) match {
@@ -647,10 +648,10 @@ trait Implicits {
647648

648649
if (Statistics.canEnable) Statistics.incCounter(typedImplicits)
649650

650-
val itree2 = if (isView) treeInfo.dissectApplied(itree1).callee
651-
else adapt(itree1, EXPRmode, wildPt)
651+
val itree3 = if (isView) treeInfo.dissectApplied(itree2).callee
652+
else adapt(itree2, EXPRmode, wildPt)
652653

653-
typingStack.showAdapt(itree, itree2, pt, context)
654+
typingStack.showAdapt(itree0, itree3, pt, context)
654655

655656
def hasMatchingSymbol(tree: Tree): Boolean = (tree.symbol == info.sym) || {
656657
tree match {
@@ -663,21 +664,21 @@ trait Implicits {
663664

664665
if (context.hasErrors)
665666
fail("hasMatchingSymbol reported error: " + context.firstError.get.errMsg)
666-
else if (isLocal && !hasMatchingSymbol(itree1))
667+
else if (isLocal && !hasMatchingSymbol(itree2))
667668
fail("candidate implicit %s is shadowed by %s".format(
668-
info.sym.fullLocationString, itree1.symbol.fullLocationString))
669+
info.sym.fullLocationString, itree2.symbol.fullLocationString))
669670
else {
670671
val tvars = undetParams map freshVar
671672
def ptInstantiated = pt.instantiateTypeParams(undetParams, tvars)
672673

673-
if (matchesPt(itree2.tpe, ptInstantiated, undetParams)) {
674+
if (matchesPt(itree3.tpe, ptInstantiated, undetParams)) {
674675
if (tvars.nonEmpty)
675676
typingLog("solve", ptLine("tvars" -> tvars, "tvars.constr" -> tvars.map(_.constr)))
676677

677-
val targs = solvedTypes(tvars, undetParams, undetParams map varianceInType(pt), upper = false, lubDepth(itree2.tpe :: pt :: Nil))
678+
val targs = solvedTypes(tvars, undetParams, undetParams map varianceInType(pt), upper = false, lubDepth(itree3.tpe :: pt :: Nil))
678679

679680
// #2421: check that we correctly instantiated type parameters outside of the implicit tree:
680-
checkBounds(itree2, NoPrefix, NoSymbol, undetParams, targs, "inferred ")
681+
checkBounds(itree3, NoPrefix, NoSymbol, undetParams, targs, "inferred ")
681682
context.firstError match {
682683
case Some(err) =>
683684
return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + err.errMsg)
@@ -693,7 +694,7 @@ trait Implicits {
693694
if (okParams.isEmpty) EmptyTreeTypeSubstituter
694695
else {
695696
val subst = new TreeTypeSubstituter(okParams, okArgs)
696-
subst traverse itree2
697+
subst traverse itree3
697698
notifyUndetparamsInferred(okParams, okArgs)
698699
subst
699700
}
@@ -711,23 +712,23 @@ trait Implicits {
711712
// This is just called for the side effect of error detection,
712713
// see SI-6966 to see what goes wrong if we use the result of this
713714
// as the SearchResult.
714-
itree2 match {
715-
case TypeApply(fun, args) => typedTypeApply(itree2, EXPRmode, fun, args)
716-
case Apply(TypeApply(fun, args), _) => typedTypeApply(itree2, EXPRmode, fun, args) // t2421c
715+
itree3 match {
716+
case TypeApply(fun, args) => typedTypeApply(itree3, EXPRmode, fun, args)
717+
case Apply(TypeApply(fun, args), _) => typedTypeApply(itree3, EXPRmode, fun, args) // t2421c
717718
case t => t
718719
}
719720

720721
context.firstError match {
721722
case Some(err) =>
722723
fail("typing TypeApply reported errors for the implicit tree: " + err.errMsg)
723724
case None =>
724-
val result = new SearchResult(itree2, subst)
725+
val result = new SearchResult(unsuppressMacroExpansion(itree3), subst)
725726
if (Statistics.canEnable) Statistics.incCounter(foundImplicits)
726727
typingLog("success", s"inferred value of type $ptInstantiated is $result")
727728
result
728729
}
729730
}
730-
else fail("incompatible: %s does not match expected type %s".format(itree2.tpe, ptInstantiated))
731+
else fail("incompatible: %s does not match expected type %s".format(itree3.tpe, ptInstantiated))
731732
}
732733
}
733734
catch {

src/reflect/scala/reflect/macros/Enclosures.scala

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,6 @@ trait Enclosures {
4545
*/
4646
def enclosingMacros: List[BlackboxContext]
4747

48-
/** Information about one of the currently considered implicit candidates.
49-
* Candidates are used in plural form, because implicit parameters may themselves have implicit parameters,
50-
* hence implicit searches can recursively trigger other implicit searches.
51-
*
52-
* Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion.
53-
* If we're in an implicit macro being expanded, it's included in this list.
54-
*
55-
* Unlike `openImplicits`, this is a val, which means that it gets initialized when the context is created
56-
* and always stays the same regardless of whatever happens during macro expansion.
57-
*/
58-
def enclosingImplicits: List[ImplicitCandidate]
59-
6048
/** Tries to guess a position for the enclosing application.
6149
* But that is simple, right? Just dereference `pos` of `macroApplication`? Not really.
6250
* 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.

src/reflect/scala/reflect/macros/Typers.scala

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,6 @@ trait Typers {
2323
*/
2424
def openMacros: List[BlackboxContext]
2525

26-
/** Information about one of the currently considered implicit candidates.
27-
* Candidates are used in plural form, because implicit parameters may themselves have implicit parameters,
28-
* hence implicit searches can recursively trigger other implicit searches.
29-
*
30-
* `pre` and `sym` provide information about the candidate itself.
31-
* `pt` and `tree` store the parameters of the implicit search the candidate is participating in.
32-
*/
33-
case class ImplicitCandidate(pre: Type, sym: Symbol, pt: Type, tree: Tree)
34-
35-
/** Information about one of the currently considered implicit candidates.
36-
* Candidates are used in plural form, because implicit parameters may themselves have implicit parameters,
37-
* hence implicit searches can recursively trigger other implicit searches.
38-
*
39-
* Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion.
40-
* If we're in an implicit macro being expanded, it's included in this list.
41-
*
42-
* Unlike `enclosingImplicits`, this is a def, which means that it gets recalculated on every invocation,
43-
* so it might change depending on what is going on during macro expansion.
44-
*/
45-
def openImplicits: List[ImplicitCandidate]
46-
4726
/** Typechecks the provided tree against the expected type `pt` in the macro callsite context.
4827
*
4928
* If `silent` is false, `TypecheckException` will be thrown in case of a typecheck error.

src/reflect/scala/reflect/macros/WhiteboxContext.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,37 @@ trait WhiteboxContext extends BlackboxContext {
4040
/** @inheritdoc
4141
*/
4242
def enclosingMacros: List[WhiteboxContext]
43+
44+
/** Information about one of the currently considered implicit candidates.
45+
* Candidates are used in plural form, because implicit parameters may themselves have implicit parameters,
46+
* hence implicit searches can recursively trigger other implicit searches.
47+
*
48+
* `pre` and `sym` provide information about the candidate itself.
49+
* `pt` and `tree` store the parameters of the implicit search the candidate is participating in.
50+
*/
51+
case class ImplicitCandidate(pre: Type, sym: Symbol, pt: Type, tree: Tree)
52+
53+
/** Information about one of the currently considered implicit candidates.
54+
* Candidates are used in plural form, because implicit parameters may themselves have implicit parameters,
55+
* hence implicit searches can recursively trigger other implicit searches.
56+
*
57+
* Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion.
58+
* If we're in an implicit macro being expanded, it's included in this list.
59+
*
60+
* Unlike `enclosingImplicits`, this is a def, which means that it gets recalculated on every invocation,
61+
* so it might change depending on what is going on during macro expansion.
62+
*/
63+
def openImplicits: List[ImplicitCandidate]
64+
65+
/** Information about one of the currently considered implicit candidates.
66+
* Candidates are used in plural form, because implicit parameters may themselves have implicit parameters,
67+
* hence implicit searches can recursively trigger other implicit searches.
68+
*
69+
* Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion.
70+
* If we're in an implicit macro being expanded, it's included in this list.
71+
*
72+
* Unlike `openImplicits`, this is a val, which means that it gets initialized when the context is created
73+
* and always stays the same regardless of whatever happens during macro expansion.
74+
*/
75+
def enclosingImplicits: List[ImplicitCandidate]
4376
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Test_2.scala:2: error: I don't like classes that contain integers
2+
println(implicitly[Foo[C1]])
3+
^
4+
one error found
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import scala.reflect.macros.BlackboxContext
2+
import scala.language.experimental.macros
3+
4+
trait Foo[T]
5+
6+
class C1(val x: Int)
7+
class C2(val x: String)
8+
9+
trait LowPriority {
10+
implicit def lessSpecific[T]: Foo[T] = null
11+
}
12+
13+
object Foo extends LowPriority {
14+
implicit def moreSpecific[T]: Foo[T] = macro Macros.impl[T]
15+
}
16+
17+
object Macros {
18+
def impl[T: c.WeakTypeTag](c: BlackboxContext) = {
19+
import c.universe._
20+
val tpe = weakTypeOf[T]
21+
if (tpe.members.exists(_.typeSignature =:= typeOf[Int]))
22+
c.abort(c.enclosingPosition, "I don't like classes that contain integers")
23+
q"new Foo[$tpe]{ override def toString = ${tpe.toString} }"
24+
}
25+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test extends App {
2+
println(implicitly[Foo[C1]])
3+
println(implicitly[Foo[C2]])
4+
}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
Test_2.scala:7: Iso.materializeIso is not a valid implicit value for Iso[Test.Foo,L] because:
2-
hasMatchingSymbol reported error: type mismatch;
1+
Test_2.scala:7: error: type mismatch;
32
found : Iso[Test.Foo,(Int, String, Boolean)]
43
required: Iso[Test.Foo,Nothing]
54
Note: (Int, String, Boolean) >: Nothing, but trait Iso is invariant in type U.
65
You may wish to define U as -U instead. (SLS 4.5)
76
val equiv = foo(Foo(23, "foo", true))
87
^
9-
Test_2.scala:7: error: could not find implicit value for parameter iso: Iso[Test.Foo,L]
10-
val equiv = foo(Foo(23, "foo", true))
11-
^
128
one error found

test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import scala.reflect.macros.BlackboxContext
1+
import scala.reflect.macros.WhiteboxContext
22
import language.experimental.macros
33

44
trait Complex[T]
55

66
class Foo(val foo: Foo)
77

88
object Complex {
9-
def impl[T: c.WeakTypeTag](c: BlackboxContext): c.Expr[Complex[T]] = {
9+
def impl[T: c.WeakTypeTag](c: WhiteboxContext): c.Expr[Complex[T]] = {
1010
import c.universe._
1111
val tpe = weakTypeOf[T]
1212
for (f <- tpe.declarations.collect{case f: TermSymbol if f.isParamAccessor && !f.isMethod => f}) {

test/files/run/macro-sip19-revised/Impls_Macros_1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import scala.reflect.macros.BlackboxContext
1+
import scala.reflect.macros.WhiteboxContext
22

33
object Macros {
4-
def impl(c: BlackboxContext) = {
4+
def impl(c: WhiteboxContext) = {
55
import c.universe._
66

77
val inscope = c.inferImplicitValue(c.mirror.staticClass("SourceLocation").toType)

test/files/run/macro-sip19/Impls_Macros_1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import scala.reflect.macros.BlackboxContext
1+
import scala.reflect.macros.WhiteboxContext
22

33
object Macros {
4-
def impl(c: BlackboxContext) = {
4+
def impl(c: WhiteboxContext) = {
55
import c.universe._
66
val Apply(fun, args) = c.enclosingImplicits(0).tree
77
val fileName = fun.pos.source.file.file.getName
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
null
2+
C2
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import scala.reflect.macros.WhiteboxContext
2+
import scala.language.experimental.macros
3+
4+
trait Foo[T]
5+
6+
class C1(val x: Int)
7+
class C2(val x: String)
8+
9+
trait LowPriority {
10+
implicit def lessSpecific[T]: Foo[T] = null
11+
}
12+
13+
object Foo extends LowPriority {
14+
implicit def moreSpecific[T]: Foo[T] = macro Macros.impl[T]
15+
}
16+
17+
object Macros {
18+
def impl[T: c.WeakTypeTag](c: WhiteboxContext) = {
19+
import c.universe._
20+
val tpe = weakTypeOf[T]
21+
if (tpe.members.exists(_.typeSignature =:= typeOf[Int]))
22+
c.abort(c.enclosingPosition, "I don't like classes that contain integers")
23+
q"new Foo[$tpe]{ override def toString = ${tpe.toString} }"
24+
}
25+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test extends App {
2+
println(implicitly[Foo[C1]])
3+
println(implicitly[Foo[C2]])
4+
}

0 commit comments

Comments
 (0)