diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2014-10-23 16:26:46 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2014-10-27 22:34:33 +1000 |
commit | 337b52b298be95df0b4b9a4bacb7c4b802620cf6 (patch) | |
tree | b9e211946e905a455082bf56dbd8095a5515b7ff | |
parent | 850899b1c9347e804763a258f64d54cef0ffd69a (diff) | |
download | scala-337b52b298be95df0b4b9a4bacb7c4b802620cf6.tar.gz scala-337b52b298be95df0b4b9a4bacb7c4b802620cf6.tar.bz2 scala-337b52b298be95df0b4b9a4bacb7c4b802620cf6.zip |
SI-8934 Fix whitebox extractor macros in the pres. compiler
The code that "aligns" patterns and extractors assumes that it can
look at the type of the unapply method to figure the arity of the
extractor.
However, the result type of a whitebox macro does not tell the
whole story, only after expanding an application of that macro do
we know the result type.
During regular compilation, this isn't a problem, as the macro
application is expanded to a call to a synthetic unapply:
{
class anon$1 {
def unapply(tree: Any): Option[(Tree, List[Treed])]
}
new anon$1
}.unapply(<unapply selector>)
In the presentation compiler, however, we now use
`-Ymacro-expand:discard`, which expands macros only to compute
the type of the application (and to allow the macro to issue
warnings/errors). The original application is retained in the
typechecked tree, modified only by attributing the potentially-sharper
type taken from the expanded macro.
This was done to improve hyperlinking support in the IDE.
This commit passes `sel.tpe` (which is the type computed by the
macro expansion) to `unapplyMethodTypes`, rather than using
the `finalResultType` of the unapply method.
This is tested with a presentation compiler test (which closely
mimics the reported bug), and with a pos test that also exercises
`-Ymacro-expand:discard`. Prior to this patch, they used to fail with:
too many patterns for trait api: expected 1, found 2
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala | 9 | ||||
-rw-r--r-- | test/files/pos/t8934a/A_1.scala | 18 | ||||
-rw-r--r-- | test/files/pos/t8934a/Test_2.flags | 1 | ||||
-rw-r--r-- | test/files/pos/t8934a/Test_2.scala | 12 | ||||
-rw-r--r-- | test/files/presentation/quasiquotes.flags | 0 | ||||
-rw-r--r-- | test/files/presentation/t8934.check | 2 | ||||
-rw-r--r-- | test/files/presentation/t8934/Runner.scala | 27 | ||||
-rw-r--r-- | test/files/presentation/t8934/src/Source.scala | 10 |
8 files changed, 74 insertions, 5 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala index 79f5e3bee8..7fcfdf4868 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala @@ -73,9 +73,7 @@ trait ScalacPatternExpanders { * Unfortunately the MethodType does not carry the information of whether * it was unapplySeq, so we have to funnel that information in separately. */ - def unapplyMethodTypes(method: Type, isSeq: Boolean): Extractor = { - val whole = firstParamType(method) - val result = method.finalResultType + def unapplyMethodTypes(whole: Type, result: Type, isSeq: Boolean): Extractor = { val expanded = ( if (result =:= BooleanTpe) Nil else typeOfMemberNamedGet(result) match { @@ -126,9 +124,10 @@ trait ScalacPatternExpanders { val patterns = newPatterns(args) val isSeq = sel.symbol.name == nme.unapplySeq val isUnapply = sel.symbol.name == nme.unapply + val extractor = sel.symbol.name match { - case nme.unapply => unapplyMethodTypes(fn.tpe, isSeq = false) - case nme.unapplySeq => unapplyMethodTypes(fn.tpe, isSeq = true) + case nme.unapply => unapplyMethodTypes(firstParamType(fn.tpe), sel.tpe, isSeq = false) + case nme.unapplySeq => unapplyMethodTypes(firstParamType(fn.tpe), sel.tpe, isSeq = true) case _ => applyMethodTypes(fn.tpe) } diff --git a/test/files/pos/t8934a/A_1.scala b/test/files/pos/t8934a/A_1.scala new file mode 100644 index 0000000000..6c1f29d030 --- /dev/null +++ b/test/files/pos/t8934a/A_1.scala @@ -0,0 +1,18 @@ +import language.experimental.macros +import reflect.macros.whitebox.Context + +object Unapply { + def impl1(c: Context)(a: c.Tree): c.Tree = { + import c.universe._ + q"(new { def unapply[T](a: String): Option[(Int, String)] = ??? }).unapply($a)" + } + def unapply(a: Any): Any = macro impl1 +} + +object UnapplySeq { + def impl1(c: Context)(a: c.Tree): c.Tree = { + import c.universe._ + q"(new { def unapplySeq[T](a: String): Option[(Int, Seq[String])] = ??? }).unapplySeq($a)" + } + def unapplySeq(a: Any): Any = macro impl1 +} diff --git a/test/files/pos/t8934a/Test_2.flags b/test/files/pos/t8934a/Test_2.flags new file mode 100644 index 0000000000..618dfe2b75 --- /dev/null +++ b/test/files/pos/t8934a/Test_2.flags @@ -0,0 +1 @@ +-Ystop-after:typer -Ymacro-expand:discard -nowarn diff --git a/test/files/pos/t8934a/Test_2.scala b/test/files/pos/t8934a/Test_2.scala new file mode 100644 index 0000000000..e1792ed3c5 --- /dev/null +++ b/test/files/pos/t8934a/Test_2.scala @@ -0,0 +1,12 @@ +object Test { + "" match { + case Unapply(a, b) => + a: Int + b: String + case UnapplySeq(a, b1, b2) => + a: Int + b1: String + b2: String + } +} +// These used to fail `too many patterns` under -Ymacro-expand:discard diff --git a/test/files/presentation/quasiquotes.flags b/test/files/presentation/quasiquotes.flags new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/files/presentation/quasiquotes.flags diff --git a/test/files/presentation/t8934.check b/test/files/presentation/t8934.check new file mode 100644 index 0000000000..0ece87f808 --- /dev/null +++ b/test/files/presentation/t8934.check @@ -0,0 +1,2 @@ +reload: Source.scala +Test OK diff --git a/test/files/presentation/t8934/Runner.scala b/test/files/presentation/t8934/Runner.scala new file mode 100644 index 0000000000..944f458391 --- /dev/null +++ b/test/files/presentation/t8934/Runner.scala @@ -0,0 +1,27 @@ +import scala.tools.nsc.interactive.tests.InteractiveTest +import scala.reflect.internal.util.SourceFile +import scala.tools.nsc.interactive.Response + +object Test extends InteractiveTest { + + override def execute(): Unit = { + val src = loadSourceAndWaitUntilTypechecked("Source.scala") + checkErrors(src) + } + + private def loadSourceAndWaitUntilTypechecked(sourceName: String): SourceFile = { + val sourceFile = sourceFiles.find(_.file.name == sourceName).head + askReload(List(sourceFile)).get + askLoadedTyped(sourceFile).get + sourceFile + } + + private def checkErrors(source: SourceFile): Unit = compiler.getUnitOf(source) match { + case Some(unit) => + val problems = unit.problems.toList + if(problems.isEmpty) reporter.println("Test OK") + else problems.foreach(problem => reporter.println(problem.msg)) + + case None => reporter.println("No compilation unit found for " + source.file.name) + } +} diff --git a/test/files/presentation/t8934/src/Source.scala b/test/files/presentation/t8934/src/Source.scala new file mode 100644 index 0000000000..769c8fd38b --- /dev/null +++ b/test/files/presentation/t8934/src/Source.scala @@ -0,0 +1,10 @@ +class Quasi { + import reflect.runtime.universe._ + + def test: Unit = { + (null: Any) match { + case q"$foo($bar)" => + } + () + } +} |