Skip to content

Commit

Permalink
SI-6623 Avoid $iw wrappers in REPL
Browse files Browse the repository at this point in the history
Use a conventional import to bump context depth in REPL templates.

Code is still ordinarily wrapped in a `$read` object.

This is a step toward 6623-like transparency.

`retronym` takes the blame for this innovation.
`adriaanm` collaborated in its commission.
`somsnytt` batted clean-up.
  • Loading branch information
retronym authored and som-snytt committed Jan 31, 2017
1 parent 7afdd10 commit 6d8c565
Show file tree
Hide file tree
Showing 39 changed files with 472 additions and 498 deletions.
Expand Up @@ -852,7 +852,7 @@ trait ContextErrors {
} catch {
// the code above tries various tricks to detect the relevant portion of the stack trace
// if these tricks fail, just fall back to uninformative, but better than nothing, getMessage
case NonFatal(ex) => // currently giving a spurious warning, see SI-6994
case NonFatal(ex) =>
macroLogVerbose("got an exception when processing a macro generated exception\n" +
"offender = " + stackTraceString(realex) + "\n" +
"error = " + stackTraceString(ex))
Expand Down
312 changes: 165 additions & 147 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -552,8 +552,10 @@ trait Namers extends MethodSynthesis {
val Import(expr, selectors) = tree
val base = expr.tpe

def checkNotRedundant(pos: Position, from: Name, to0: Name) {
def check(to: Name) = {
// warn proactively if specific import loses to definition in scope,
// since it may result in desired implicit not imported into scope.
def checkNotRedundant(pos: Position, from: Name, to0: Name): Unit = {
def check(to: Name): Unit = {
val e = context.scope.lookupEntry(to)

if (e != null && e.owner == context.scope && e.sym.exists)
Expand All @@ -565,7 +567,8 @@ trait Namers extends MethodSynthesis {
defSym andAlso (typer.permanentlyHiddenWarning(pos, to0, _))
}
}
if (!tree.symbol.isSynthetic && expr.symbol != null && !expr.symbol.isInterpreterWrapper) {
def isReplMagic(importInfo: ImportInfo): Boolean = importInfo.isExplicitImport(Interpreter_iw.name)
if (!tree.symbol.isSynthetic && expr.symbol != null && !context.imports.exists(isReplMagic)) {
if (base.member(from) != NoSymbol)
check(to0)
if (base.member(from.toTypeName) != NoSymbol)
Expand Down Expand Up @@ -596,7 +599,6 @@ trait Namers extends MethodSynthesis {
checkNotRedundant(tree.pos withPoint fromPos, from, to)
}
}

def noDuplicates(names: List[Name], check: DuplicatesErrorKinds.Value) {
def loop(xs: List[Name]): Unit = xs match {
case Nil => ()
Expand All @@ -606,6 +608,7 @@ trait Namers extends MethodSynthesis {
}
loop(names filterNot (x => x == null || x == nme.WILDCARD))
}

selectors foreach checkSelector

// checks on the whole set
Expand Down Expand Up @@ -1644,7 +1647,6 @@ trait Namers extends MethodSynthesis {
}
}


/** Given a case class
* case class C[Ts] (ps: Us)
* Add the following methods to toScope:
Expand Down
38 changes: 25 additions & 13 deletions src/partest-extras/scala/tools/partest/ReplTest.scala
Expand Up @@ -31,20 +31,17 @@ abstract class ReplTest extends DirectTest {
val s = settings
log("eval(): settings = " + s)
val lines = ILoop.runForTranscript(code, s, inSession = inSession).lines
(if (welcoming) {
val welcome = "(Welcome to Scala).*".r
//val welcome = Regex.quote(header.lines.next).r
//val version = "(.*version).*".r // version on separate line?
//var inHead = false
lines map {
//case s @ welcome() => inHead = true ; s
//case version(s) if inHead => inHead = false ; s
case welcome(s) => s
case s => s
val headless =
if (welcoming) {
val welcome = "(Welcome to Scala).*".r
lines map {
case welcome(s) => s
case s => s
}
} else {
lines drop header.lines.size
}
} else {
lines drop header.lines.size
}) map normalize
headless.map(normalize)
}
def show() = eval() foreach println
}
Expand All @@ -54,6 +51,21 @@ trait Welcoming { this: ReplTest =>
override def welcoming = true
}

/** Strip Any.toString's id@abcdef16 hashCodes. These are generally at end of result lines. */
trait Hashless extends ReplTest {
import Hashless._
override def normalize(s: String) = {
val n = super.normalize(s)
n match {
case hashless(prefix) => s"$prefix@XXXXXXXX"
case _ => n
}
}
}
object Hashless {
private val hashless = "(.*)@[a-fA-F0-9]+".r
}

/** Run a REPL test from a session transcript.
* The `session` should be a triple-quoted String starting
* with the `Type in expressions` message and ending
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/internal/Definitions.scala
Expand Up @@ -525,6 +525,8 @@ trait Definitions extends api.StandardDefinitions {
def MacroContextTreeType = BlackboxContextClass.map(sym => getTypeMember(sym, tpnme.Tree))
lazy val MacroImplAnnotation = requiredClass[scala.reflect.macros.internal.macroImpl]

lazy val Interpreter_iw = getModuleIfDefined("scala.tools.nsc.interpreter.$u007B$u007B") //{{

lazy val StringContextClass = requiredClass[scala.StringContext]

// SI-8392 a reflection universe on classpath may not have
Expand Down
4 changes: 2 additions & 2 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -323,8 +323,8 @@ trait StdNames {
val EXCEPTION_RESULT_PREFIX = "exceptionResult"
val EXPAND_SEPARATOR_STRING = "$$"
val FRESH_TERM_NAME_PREFIX = "x$"
val INTERPRETER_IMPORT_WRAPPER = "$iw"
val LOCALDUMMY_PREFIX = "<local " // owner of local blocks
val INTERPRETER_WRAPPER = "$read"
val PROTECTED_PREFIX = "protected$"
val PROTECTED_SET_PREFIX = PROTECTED_PREFIX + "set"
val SUPER_PREFIX_STRING = "super$"
Expand Down Expand Up @@ -383,7 +383,7 @@ trait StdNames {
def isLocalName(name: Name) = name endsWith LOCAL_SUFFIX_STRING
def isLoopHeaderLabel(name: Name) = (name startsWith WHILE_PREFIX) || (name startsWith DO_WHILE_PREFIX)
def isProtectedAccessorName(name: Name) = name startsWith PROTECTED_PREFIX
def isReplWrapperName(name: Name) = name containsName INTERPRETER_IMPORT_WRAPPER
def isReplWrapperName(name: Name) = name containsName INTERPRETER_WRAPPER
def isSetterName(name: Name) = name endsWith SETTER_SUFFIX
def isTraitSetterName(name: Name) = isSetterName(name) && (name containsName TRAIT_SETTER_SEPARATOR_STRING)
def isSingletonName(name: Name) = name endsWith SINGLETON_SUFFIX
Expand Down
10 changes: 2 additions & 8 deletions src/reflect/scala/reflect/internal/Symbols.scala
Expand Up @@ -674,12 +674,6 @@ trait Symbols extends api.Symbols { self: SymbolTable =>

final def isOverridableMember = !(isClass || isEffectivelyFinal) && safeOwner.isClass

/** Does this symbol denote a wrapper created by the repl? */
final def isInterpreterWrapper = (
(this hasFlag MODULE)
&& isTopLevel
&& nme.isReplWrapperName(name)
)

/** In our current architecture, symbols for top-level classes and modules
* are created as dummies. Package symbols just call newClass(name) or newModule(name) and
Expand Down Expand Up @@ -852,7 +846,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
final def skipConstructor: Symbol = if (isConstructor) owner else this

/** Conditions where we omit the prefix when printing a symbol, to avoid
* unpleasantries like Predef.String, $iw.$iw.Foo and <empty>.Bippy.
* unpleasantries like Predef.String, $read.Foo and <empty>.Bippy.
*/
final def isOmittablePrefix = /*!settings.debug.value &&*/ (
UnqualifiedOwners(skipPackageObject)
Expand All @@ -861,7 +855,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
def isEmptyPrefix = (
isEffectiveRoot // has no prefix for real, <empty> or <root>
|| isAnonOrRefinementClass // has uninteresting <anon> or <refinement> prefix
|| nme.isReplWrapperName(name) // has ugly $iw. prefix (doesn't call isInterpreterWrapper due to nesting)
|| nme.isReplWrapperName(name) // $read.Foo or $read.INSTANCE.Foo
)
def isFBounded = info match {
case TypeBounds(_, _) => info.baseTypeSeq exists (_ contains this)
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Expand Up @@ -316,6 +316,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
definitions.BlackboxContextClass
definitions.WhiteboxContextClass
definitions.MacroImplAnnotation
definitions.Interpreter_iw
definitions.StringContextClass
definitions.QuasiquoteClass
definitions.QuasiquoteClass_api
Expand Down
76 changes: 26 additions & 50 deletions src/repl/scala/tools/nsc/interpreter/IMain.scala
Expand Up @@ -7,7 +7,6 @@ package scala
package tools.nsc
package interpreter

import PartialFunction.cond
import scala.language.implicitConversions
import scala.beans.BeanProperty
import scala.collection.mutable
Expand Down Expand Up @@ -314,12 +313,12 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
def originalPath(name: String): String = originalPath(TermName(name))
def originalPath(name: Name): String = translateOriginalPath(typerOp path name)
def originalPath(sym: Symbol): String = translateOriginalPath(typerOp path sym)

/** For class based repl mode we use an .INSTANCE accessor. */
val readInstanceName = if(isClassBased) ".INSTANCE" else ""
def translateOriginalPath(p: String): String = {
val readName = java.util.regex.Matcher.quoteReplacement(sessionNames.read)
p.replaceFirst(readName, readName + readInstanceName)
}
val readInstanceName = if (isClassBased) ".INSTANCE" else ""
def translateOriginalPath(p: String): String =
if (isClassBased) p.replace(sessionNames.read, sessionNames.read + readInstanceName) else p

def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName

def translatePath(path: String) = {
Expand All @@ -329,7 +328,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends

/** If path represents a class resource in the default package,
* see if the corresponding symbol has a class file that is a REPL artifact
* residing at a different resource path. Translate X.class to $line3/$read$$iw$$iw$X.class.
* residing at a different resource path. Translate X.class to $line3/$read$X.class.
*/
def translateSimpleResource(path: String): Option[String] = {
if (!(path contains '/') && (path endsWith ".class")) {
Expand Down Expand Up @@ -701,12 +700,9 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends

val unwrapped = unwrap(t)

// Example input: $line3.$read$$iw$$iw$
val classNameRegex = (naming.lineRegex + ".*").r
def isWrapperInit(x: StackTraceElement) = cond(x.getClassName) {
case classNameRegex() if x.getMethodName == nme.CONSTRUCTOR.decoded => true
}
val stackTrace = unwrapped stackTracePrefixString (!isWrapperInit(_))
def notWrapperInit(x: StackTraceElement) = !naming.isLineWrapperClassName(x.getClassName) || x.getMethodName != nme.CONSTRUCTOR.decoded

val stackTrace = unwrapped.stackTracePrefixString(notWrapperInit _)

withLastExceptionLock[String]({
directBind[Throwable]("lastException", unwrapped)(StdReplTags.tagOfThrowable, classTag[Throwable])
Expand Down Expand Up @@ -842,7 +838,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
* append to objectName to access anything bound by request.
*/
lazy val ComputedImports(headerPreamble, importsPreamble, importsTrailer, accessPath) =
exitingTyper(importsCode(referencedNames.toSet, ObjectSourceCode, definesClass, generousImports))
exitingTyper(importsCode(referencedNames.toSet, definesClass, generousImports))

/** the line of code to compute */
def toCompute = line
Expand All @@ -855,7 +851,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends

/** generate the source code for the object that computes this request */
abstract class Wrapper extends IMain.CodeAssembler[MemberHandler] {
def path = originalPath("$intp")
//def path = originalPath("$intp")
def envLines = {
if (!isReplPower) Nil // power mode only for now
else {
Expand All @@ -876,37 +872,23 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
/** A format string with %s for $read, specifying the wrapper definition. */
def preambleHeader: String

/** Like preambleHeader for an import wrapper. */
def prewrap: String = preambleHeader + "\n"

/** Like postamble for an import wrapper. */
def postwrap: String
def postamble = importsTrailer + "\n}"
}

class ObjectBasedWrapper extends Wrapper {
def preambleHeader = "object %s {"

def postamble = importsTrailer + "\n}"

def postwrap = "}\n"
}

class ClassBasedWrapper extends Wrapper {
def preambleHeader = "sealed class %s extends _root_.java.io.Serializable { "

/** Adds an object that instantiates the outer wrapping class. */
def postamble = s"""
|$importsTrailer
|}
override def postamble = s"""
|${super.postamble}
|object ${lineRep.readName} {
| val INSTANCE = new ${lineRep.readName}();
|}
|""".stripMargin

import nme.{ INTERPRETER_IMPORT_WRAPPER => iw }

/** Adds a val that instantiates the wrapping class. */
def postwrap = s"}\nval $iw = new $iw\n"
}

private[interpreter] lazy val ObjectSourceCode: Wrapper =
Expand Down Expand Up @@ -969,14 +951,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
}
}

//lazy val resultSymbol = lineRep.resolvePathToSymbol(fullAccessPath)
// the type symbol of the owner of the member that supplies the result value
lazy val resultSymbol = {
val sym =
lineRep.resolvePathToSymbol(fullAccessPath)
// plow through the INSTANCE member when -Yrepl-class-based
if (sym.isTerm && sym.nameString == "INSTANCE") sym.typeSignature.typeSymbol else sym
}
lazy val resultSymbol = lineRep.resolvePathToSymbol(fullAccessPath)

def applyToResultMember[T](name: Name, f: Symbol => T) = exitingTyper(f(resultSymbol.info.nonPrivateDecl(name)))

Expand All @@ -989,7 +965,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
/** Types of variables defined by this request. */
lazy val compilerTypeOf = typeMap[Type](x => x) withDefaultValue NoType
/** String representations of same. */
lazy val typeOf = typeMap[String](tp => exitingTyper(tp.toString))
lazy val typeOf = typeMap[String](tp => exitingTyper {
val s = tp.toString
if (isClassBased) s.stripPrefix("INSTANCE.") else s
})

lazy val definedSymbols = (
termNames.map(x => x -> applyToResultMember(x, x => x)) ++
Expand Down Expand Up @@ -1207,24 +1186,20 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
def withoutTruncating[A](body: => A): A = reporter withoutTruncating body

def symbolDefString(sym: Symbol) = {
/*
val ds = exitingTyper(sym.defString)
val no = List(sym.owner.name + ".this.", sym.owner.fullName + ".")
val q = TypeStrings.quieter(ds, no: _*)
Console println ss"defstr of $ds excluding $no is $q"
*/
TypeStrings.quieter(
exitingTyper(sym.defString),
sym.owner.name + ".this.",
sym.owner.fullName + "."
)
}

/** Secret bookcase entrance for repl debuggers: end the line
* with "// show" and see what's going on.
*/
val showCodeBackDoor = raw".*//\s*show\s*".r

def showCodeIfDebugging(code: String) {
/** Secret bookcase entrance for repl debuggers: end the line
* with "// show" and see what's going on.
*/
def isShow = code.lines exists (_.trim endsWith "// show")
def isShow = code.lines exists { case showCodeBackDoor() => true case _ => false }
if (isReplDebug || isShow) {
beSilentDuring(parse(code)) match {
case parse.Success(ts) =>
Expand Down Expand Up @@ -1254,6 +1229,7 @@ object IMain {
// $line3.$read$$iw$$iw$Bippy@4a6a00ca
private def removeLineWrapper(s: String) = s.replaceAll("""\$line\d+[./]\$(read|eval|print)[$.]""", "")
private def removeIWPackages(s: String) = s.replaceAll("""\$(iw|read|eval|print)[$.]""", "")
@deprecated("Use intp.naming.unmangle.", "2.12.0-M5")
def stripString(s: String) = removeIWPackages(removeLineWrapper(s))

trait CodeAssembler[T] {
Expand Down

0 comments on commit 6d8c565

Please sign in to comment.