diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala | 9 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Namers.scala | 6 | ||||
-rw-r--r-- | src/interactive/scala/tools/nsc/interactive/Global.scala | 15 | ||||
-rw-r--r-- | test/files/presentation/t8941.check | 7 | ||||
-rw-r--r-- | test/files/presentation/t8941/Runner.scala | 11 | ||||
-rw-r--r-- | test/files/presentation/t8941/src/Source.scala | 8 | ||||
-rw-r--r-- | test/files/presentation/t8941b/IdempotencyTest.scala | 73 | ||||
-rw-r--r-- | test/files/presentation/t8941b/Test.scala | 53 |
8 files changed, 173 insertions, 9 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index ba183fe3e6..0aa62d771e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -212,7 +212,9 @@ trait MethodSynthesis { List(cd, mdef) case _ => // Shouldn't happen, but let's give ourselves a reasonable error when it does - abort("No synthetics for " + meth + ": synthetics contains " + context.unit.synthetics.keys.mkString(", ")) + context.error(cd.pos, s"Internal error: Symbol for synthetic factory method not found among ${context.unit.synthetics.keys.mkString(", ")}") + // Soldier on for the sake of the presentation compiler + List(cd) } case _ => stat :: Nil @@ -355,8 +357,9 @@ trait MethodSynthesis { def derivedSym: Symbol = { // Only methods will do! Don't want to pick up any stray // companion objects of the same name. - val result = enclClass.info decl name suchThat (x => x.isMethod && x.isSynthetic) - assert(result != NoSymbol, "not found: "+name+" in "+enclClass+" "+enclClass.info.decls) + val result = enclClass.info decl name filter (x => x.isMethod && x.isSynthetic) + if (result == NoSymbol || result.isOverloaded) + context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $name in $enclClass / ${enclClass.info.decls}") result } def derivedTree: DefDef = diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index e876d4a6af..8bdf5c33ba 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -296,7 +296,7 @@ trait Namers extends MethodSynthesis { } tree.symbol match { case NoSymbol => try dispatch() catch typeErrorHandler(tree, this.context) - case sym => enterExistingSym(sym) + case sym => enterExistingSym(sym, tree) } } @@ -736,7 +736,9 @@ trait Namers extends MethodSynthesis { } // Hooks which are overridden in the presentation compiler - def enterExistingSym(sym: Symbol): Context = this.context + def enterExistingSym(sym: Symbol, tree: Tree): Context = { + this.context + } def enterIfNotThere(sym: Symbol) { } def enterSyntheticSym(tree: Tree): Symbol = { diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 174254d523..7df809b6ff 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -64,7 +64,9 @@ trait InteractiveAnalyzer extends Analyzer { // that case the definitions that were already attributed as // well as any default parameters of such methods need to be // re-entered in the current scope. - override def enterExistingSym(sym: Symbol): Context = { + // + // Tested in test/files/presentation/t8941b + override def enterExistingSym(sym: Symbol, tree: Tree): Context = { if (sym != null && sym.owner.isTerm) { enterIfNotThere(sym) if (sym.isLazy) @@ -72,8 +74,13 @@ trait InteractiveAnalyzer extends Analyzer { for (defAtt <- sym.attachments.get[DefaultsOfLocalMethodAttachment]) defAtt.defaultGetters foreach enterIfNotThere + } else if (sym != null && sym.isClass && sym.isImplicit) { + val owningInfo = sym.owner.info + val existingDerivedSym = owningInfo.decl(sym.name.toTermName).filter(sym => sym.isSynthetic && sym.isMethod) + existingDerivedSym.alternatives foreach (owningInfo.decls.unlink) + enterImplicitWrapper(tree.asInstanceOf[ClassDef]) } - super.enterExistingSym(sym) + super.enterExistingSym(sym, tree) } override def enterIfNotThere(sym: Symbol) { val scope = context.scope @@ -732,7 +739,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } } - private def reloadSource(source: SourceFile) { + private[interactive] def reloadSource(source: SourceFile) { val unit = new RichCompilationUnit(source) unitOfFile(source.file) = unit toBeRemoved -= source.file @@ -781,7 +788,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } /** A fully attributed tree located at position `pos` */ - private def typedTreeAt(pos: Position): Tree = getUnit(pos.source) match { + private[interactive] def typedTreeAt(pos: Position): Tree = getUnit(pos.source) match { case None => reloadSources(List(pos.source)) try typedTreeAt(pos) diff --git a/test/files/presentation/t8941.check b/test/files/presentation/t8941.check new file mode 100644 index 0000000000..341804903a --- /dev/null +++ b/test/files/presentation/t8941.check @@ -0,0 +1,7 @@ +reload: Source.scala + +askType at Source.scala(6,7) +================================================================================ +[response] askTypeAt (6,7) +scala.this.Predef.??? +================================================================================ diff --git a/test/files/presentation/t8941/Runner.scala b/test/files/presentation/t8941/Runner.scala new file mode 100644 index 0000000000..0a8923a583 --- /dev/null +++ b/test/files/presentation/t8941/Runner.scala @@ -0,0 +1,11 @@ +import scala.tools.nsc.interactive.tests.InteractiveTest + +object Test extends InteractiveTest { + override def runDefaultTests() { + // make sure typer is done.. the virtual pattern matcher might translate + // some trees and mess up positions. But we'll catch it red handed! + // sourceFiles foreach (src => askLoadedTyped(src).get) + super.runDefaultTests() + } + +} diff --git a/test/files/presentation/t8941/src/Source.scala b/test/files/presentation/t8941/src/Source.scala new file mode 100644 index 0000000000..7438cccb03 --- /dev/null +++ b/test/files/presentation/t8941/src/Source.scala @@ -0,0 +1,8 @@ +object Foo { + implicit class MatCreator(val ctx: StringContext) extends AnyVal { + def m(args: Any*): Unit = { + ctx.checkLengths(args) + } + ???/*?*/ + } +} diff --git a/test/files/presentation/t8941b/IdempotencyTest.scala b/test/files/presentation/t8941b/IdempotencyTest.scala new file mode 100644 index 0000000000..af01b36898 --- /dev/null +++ b/test/files/presentation/t8941b/IdempotencyTest.scala @@ -0,0 +1,73 @@ +package scala.tools.nsc +package interactive +package tests.core + +import reporters.{Reporter => CompilerReporter} +import scala.tools.nsc.interactive.InteractiveReporter +import scala.reflect.internal.util.SourceFile + +/** Determistically interrupts typechecking of `code` when a defintion named + * `MagicInterruptionMarker` is typechecked, and then performs a targetted + * typecheck of the tree at the specal comment marker marker + */ +abstract class IdempotencyTest { self => + private val settings = new Settings + settings.usejavacp.value = true + + private object Break extends scala.util.control.ControlThrowable + + private val compilerReporter: CompilerReporter = new InteractiveReporter { + override def compiler = self.compiler + } + + object compiler extends Global(settings, compilerReporter) { + override def checkForMoreWork(pos: Position) { + } + override def signalDone(context: Context, old: Tree, result: Tree) { + // println("signalDone: " + old.toString.take(50).replaceAll("\n", "\\n")) + if (!interrupted && analyzer.lockedCount == 0 && interruptsEnabled && shouldInterrupt(result)) { + interrupted = true + val typed = typedTreeAt(markerPosition) + checkTypedTree(typed) + throw Break + } + super.signalDone(context, old, result) + } + + // we're driving manually using our own thread, disable the check here. + override def assertCorrectThread() {} + } + + import compiler._ + + private var interrupted = false + + // Extension points + protected def code: String + protected def shouldInterrupt(tree: Tree): Boolean = { + tree.symbol != null && tree.symbol.name.toString == "MagicInterruptionMarker" + } + protected def checkTypedTree(tree: Tree): Unit = {} + + + private val source: SourceFile = newSourceFile(code) + private def markerPosition: Position = source.position(code.indexOf("/*?*/")) + + def assertNoProblems() { + val problems = getUnit(source).get.problems + assert(problems.isEmpty, problems.mkString("\n")) + } + + def show() { + reloadSource(source) + try { + typedTree(source, true) + assert(false, "Expected to break out of typechecking.") + } catch { + case Break => // expected + } + assertNoProblems() + } + + def main(args: Array[String]) { show() } +} diff --git a/test/files/presentation/t8941b/Test.scala b/test/files/presentation/t8941b/Test.scala new file mode 100644 index 0000000000..7269a14286 --- /dev/null +++ b/test/files/presentation/t8941b/Test.scala @@ -0,0 +1,53 @@ +import scala.tools.nsc.interactive.tests.core.IdempotencyTest + +// At the time of writing this test, removing any part of `enterExistingSym` +// leads to a failure. +object Test { + def main(args: Array[String]) { + test(""" + object Foo { + def term { + def foo(c: String = "") = c + class MagicInterruptionMarker + foo()/*?*/ + } + } + """) + + test(""" + object Foo { + def term { + def foo = 42 + class MagicInterruptionMarker + foo/*?*/ + } + } + """) + + test(""" + object Foo { + def term { + lazy val foo = 42 + class MagicInterruptionMarker + foo/*?*/ + } + } + """) + + test(""" + object Foo { + implicit class C(val a: String) extends AnyVal + class MagicInterruptionMarker + ""/*?*/ + } + """) + } + + def test(code0: String) { + val t = new IdempotencyTest { + def code = code0 + } + t.show() + } +} + |