Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SI-8941 Idempotent presentation compilation of implicit classes #4079

Merged
merged 2 commits into from Nov 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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 = {
Expand Down
15 changes: 11 additions & 4 deletions src/interactive/scala/tools/nsc/interactive/Global.scala
Expand Up @@ -64,16 +64,23 @@ 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)
sym.lazyAccessor andAlso enterIfNotThere

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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions 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.???
================================================================================
11 changes: 11 additions & 0 deletions 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()
}

}
8 changes: 8 additions & 0 deletions 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)
}
???/*?*/
}
}
73 changes: 73 additions & 0 deletions 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() }
}
53 changes: 53 additions & 0 deletions 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()
}
}