Skip to content

Commit 54790a0

Browse files
committed
SI-6623 -Yrepl-use-magic-imports avoids nesting $iw wrappers
Rather than adding a wrapper object for each import in the session history, just use a single wrapper preceded by the imports which have been interspersed with a magic import to bump context depth. 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.
1 parent 09063fc commit 54790a0

File tree

16 files changed

+188
-125
lines changed

16 files changed

+188
-125
lines changed

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ trait ScalaSettings extends AbsScalaSettings
237237
val YmacroFresh = BooleanSetting ("-Ymacro-global-fresh-names", "Should fresh names in macros be unique across all compilation units")
238238
val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup")
239239
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects")
240+
val YreplMagicImport = BooleanSetting ("-Yrepl-use-magic-imports", "In the code the wraps REPL snippes, use magic imports to rather than nesting wrapper object/classes")
240241
val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "")
241242
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.")
242243
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ trait Contexts { self: Analyzer =>
241241
/** Equivalent to `imports.headOption`, but more efficient */
242242
def firstImport: Option[ImportInfo] = outer.firstImport
243243
protected[Contexts] def importOrNull: ImportInfo = null
244-
/** A root import is never unused and always bumps context depth. (e.g scala._ / Predef._ / java.lang._) */
244+
/** A root import is never unused and always bumps context depth. (e.g scala._ / Predef._ and magic REPL imports) */
245245
def isRootImport: Boolean = false
246246

247247
/** Types for which implicit arguments are currently searched */
@@ -490,7 +490,7 @@ trait Contexts { self: Analyzer =>
490490

491491
// The blank canvas
492492
val c = if (isImport) {
493-
val isRootImport = !tree.pos.isDefined
493+
val isRootImport = !tree.pos.isDefined || isReplImportWrapperImport(tree)
494494
new ImportContext(tree, owner, scope, unit, this, isRootImport, innerDepth(isRootImport), reporter)
495495
} else
496496
new Context(tree, owner, scope, unit, this, innerDepth(isRootImport = false), reporter)
@@ -1101,6 +1101,16 @@ trait Contexts { self: Analyzer =>
11011101
}
11021102
}
11031103

1104+
1105+
private def isReplImportWrapperImport(tree: Tree): Boolean = {
1106+
tree match {
1107+
case Import(expr, selector :: Nil) =>
1108+
// Just a syntactic check to avoid forcing typechecking of imports
1109+
selector.name.string_==(nme.INTERPRETER_IMPORT_LEVEL_UP) && owner.enclosingTopLevelClass.isInterpreterWrapper
1110+
case _ => false
1111+
}
1112+
}
1113+
11041114
} //class Context
11051115

11061116
/** Find the symbol of a simple name starting from this context.

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,10 @@ trait Namers extends MethodSynthesis {
536536
val Import(expr, selectors) = tree
537537
val base = expr.tpe
538538

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

543545
if (e != null && e.owner == context.scope && e.sym.exists)
@@ -1820,7 +1822,6 @@ trait Namers extends MethodSynthesis {
18201822
}
18211823
}
18221824

1823-
18241825
/** Given a case class
18251826
* case class C[Ts] (ps: Us)
18261827
* Add the following methods to toScope:

src/partest-extras/scala/tools/partest/ReplTest.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ trait Welcoming { this: ReplTest =>
6767
override def welcoming = true
6868
}
6969

70+
/** Strip Any.toString's id@abcdef16 hashCodes. These are generally at end of result lines. */
71+
trait Hashless extends ReplTest {
72+
import Hashless._
73+
override def normalize(s: String) = {
74+
val n = super.normalize(s)
75+
n match {
76+
case hashless(prefix) => s"$prefix@XXXXXXXX"
77+
case _ => n
78+
}
79+
}
80+
}
81+
object Hashless {
82+
private val hashless = "(.*)@[a-fA-F0-9]+".r
83+
}
84+
7085
/** Run a REPL test from a session transcript.
7186
* The `session` is read from the `.check` file.
7287
*/

src/reflect/scala/reflect/internal/StdNames.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,9 @@ trait StdNames {
332332
val EXCEPTION_RESULT_PREFIX = "exceptionResult"
333333
val EXPAND_SEPARATOR_STRING = "$$"
334334
val FRESH_TERM_NAME_PREFIX = "x$"
335+
val INTERPRETER_IMPORT_LEVEL_UP = NameTransformer.encode("{{")
335336
val INTERPRETER_IMPORT_WRAPPER = "$iw"
337+
val INTERPRETER_WRAPPER = "$read"
336338
val LOCALDUMMY_PREFIX = "<local " // owner of local blocks
337339
val PROTECTED_PREFIX = "protected$"
338340
val PROTECTED_SET_PREFIX = PROTECTED_PREFIX + "set"
@@ -392,7 +394,7 @@ trait StdNames {
392394
def isLocalName(name: Name) = name endsWith LOCAL_SUFFIX_STRING
393395
def isLoopHeaderLabel(name: Name) = (name startsWith WHILE_PREFIX) || (name startsWith DO_WHILE_PREFIX)
394396
def isProtectedAccessorName(name: Name) = name startsWith PROTECTED_PREFIX
395-
def isReplWrapperName(name: Name) = name containsName INTERPRETER_IMPORT_WRAPPER
397+
def isReplWrapperName(name: Name) = (name containsName INTERPRETER_WRAPPER) || (name containsName INTERPRETER_IMPORT_WRAPPER)
396398
def isSetterName(name: Name) = name endsWith SETTER_SUFFIX
397399
def isTraitSetterName(name: Name) = isSetterName(name) && (name containsName TRAIT_SETTER_SEPARATOR_STRING)
398400
def isSingletonName(name: Name) = name endsWith SINGLETON_SUFFIX

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -706,11 +706,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
706706
final def isOverridableMember = !(isClass || isEffectivelyFinal || isTypeParameter) && safeOwner.isClass
707707

708708
/** Does this symbol denote a wrapper created by the repl? */
709-
final def isInterpreterWrapper = (
710-
(this hasFlag MODULE)
711-
&& isTopLevel
712-
&& nme.isReplWrapperName(name)
713-
)
709+
final def isInterpreterWrapper = isTopLevel && nme.isReplWrapperName(name)
714710

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

876872
/** Conditions where we omit the prefix when printing a symbol, to avoid
877-
* unpleasantries like Predef.String, $iw.$iw.Foo and <empty>.Bippy.
873+
* unpleasantries like Predef.String, $read.$iw.Foo and <empty>.Bippy.
878874
*/
879875
final def isOmittablePrefix = /*!settings.debug.value &&*/ {
880876
// scala/bug#5941 runtime reflection can have distinct symbols representing `package scala` (from different mirrors)
@@ -886,7 +882,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
886882
def isEmptyPrefix = (
887883
isEffectiveRoot // has no prefix for real, <empty> or <root>
888884
|| isAnonOrRefinementClass // has uninteresting <anon> or <refinement> prefix
889-
|| nme.isReplWrapperName(name) // has ugly $iw. prefix (doesn't call isInterpreterWrapper due to nesting)
885+
|| nme.isReplWrapperName(name) // $read.$iw.Foo or $read.INSTANCE.$iw.Foo
890886
)
891887
def isFBounded = info match {
892888
case TypeBounds(_, _) => info.baseTypeSeq exists (_ contains this)

src/repl/scala/tools/nsc/interpreter/IMain.scala

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ package scala
1414
package tools.nsc
1515
package interpreter
1616

17-
import PartialFunction.cond
1817
import scala.language.implicitConversions
1918
import scala.collection.mutable
2019
import scala.concurrent.{ExecutionContext, Future}
@@ -76,6 +75,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
7675
def showDirectory() = replOutput.show(out)
7776

7877
lazy val isClassBased: Boolean = settings.Yreplclassbased.value
78+
private[interpreter] lazy val useMagicImport: Boolean = settings.YreplMagicImport.value
7979

8080
private[nsc] var printResults = true // whether to print result lines
8181
private[nsc] var totalSilence = false // whether to print anything
@@ -318,13 +318,11 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
318318
def originalPath(name: String): String = originalPath(TermName(name))
319319
def originalPath(name: Name): String = translateOriginalPath(typerOp path name)
320320
def originalPath(sym: Symbol): String = translateOriginalPath(typerOp path sym)
321+
321322
/** For class based repl mode we use an .INSTANCE accessor. */
322323
val readInstanceName = if (isClassBased) ".INSTANCE" else ""
323324
def translateOriginalPath(p: String): String = {
324-
if (isClassBased) {
325-
val readName = java.util.regex.Matcher.quoteReplacement(sessionNames.read)
326-
p.replaceFirst(readName, readName + readInstanceName)
327-
} else p
325+
if (isClassBased) p.replace(sessionNames.read, sessionNames.read + readInstanceName) else p
328326
}
329327
def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName
330328

@@ -335,7 +333,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
335333

336334
/** If path represents a class resource in the default package,
337335
* see if the corresponding symbol has a class file that is a REPL artifact
338-
* residing at a different resource path. Translate X.class to $line3/$read$$iw$$iw$X.class.
336+
* residing at a different resource path. Translate X.class to $line3/$read$iw$X.class.
339337
*/
340338
def translateSimpleResource(path: String): Option[String] = {
341339
if (!(path contains '/') && (path endsWith ".class")) {
@@ -711,9 +709,9 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
711709

712710
val unwrapped = unwrap(t)
713711

714-
// Example input: $line3.$read$$iw$$iw$
712+
// Example input: $line3.$read$$iw$
715713
val classNameRegex = (naming.lineRegex + ".*").r
716-
def isWrapperInit(x: StackTraceElement) = cond(x.getClassName) {
714+
def isWrapperInit(x: StackTraceElement) = PartialFunction.cond(x.getClassName) {
717715
case classNameRegex() if x.getMethodName == nme.CONSTRUCTOR.decoded => true
718716
}
719717
val stackTrace = unwrapped stackTracePrefixString (!isWrapperInit(_))
@@ -863,7 +861,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
863861

864862
/** generate the source code for the object that computes this request */
865863
abstract class Wrapper extends IMain.CodeAssembler[MemberHandler] {
866-
def path = originalPath("$intp")
867864
def envLines = {
868865
if (!isReplPower) Nil // power mode only for now
869866
else {
@@ -884,6 +881,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
884881
/** A format string with %s for $read, specifying the wrapper definition. */
885882
def preambleHeader: String
886883

884+
def postamble: String
885+
887886
/** Like preambleHeader for an import wrapper. */
888887
def prewrap: String = preambleHeader + "\n"
889888

@@ -990,11 +989,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
990989
}
991990

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

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

@@ -1007,7 +1002,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
10071002
/** Types of variables defined by this request. */
10081003
lazy val compilerTypeOf = typeMap[Type](x => x) withDefaultValue NoType
10091004
/** String representations of same. */
1010-
lazy val typeOf = typeMap[String](tp => exitingTyper(tp.toString))
1005+
lazy val typeOf = typeMap[String](tp => exitingTyper {
1006+
val s = tp.toString
1007+
if (isClassBased) s.stripPrefix("INSTANCE.") else s
1008+
})
10111009

10121010
lazy val definedSymbols = (
10131011
termNames.map(x => x -> applyToResultMember(x, x => x)) ++
@@ -1060,7 +1058,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
10601058
def typeOfTerm(id: String): Type = symbolOfTerm(id).tpe
10611059

10621060
// Given the fullName of the symbol, reflectively drill down the path
1063-
def valueOfTerm(id: String): Option[Any] = {
1061+
def valueOfTerm(id: String): Option[Any] = exitingTyper {
10641062
def value(fullName: String) = {
10651063
val mirror = runtimeMirror
10661064
import mirror.universe.{Symbol, InstanceMirror, TermName}
@@ -1088,7 +1086,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
10881086
mirror.reflect(mirrored.reflectMethod(s.asMethod).apply())
10891087
}
10901088
else {
1091-
assert(false, originalPath(s))
1089+
assert(false, fullName)
10921090
inst
10931091
}
10941092
loop(i, s, rest)
@@ -1142,6 +1140,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
11421140
)
11431141
)
11441142
}
1143+
// this is harder than getting the typed trees and fixing up the string to emit that reports types
11451144
def cleanMemberDecl(owner: Symbol, member: Name): Type =
11461145
cleanTypeAfterTyper(owner.info nonPrivateDecl member)
11471146

@@ -1267,6 +1266,7 @@ object IMain {
12671266
// $line3.$read$$iw$$iw$Bippy@4a6a00ca
12681267
private def removeLineWrapper(s: String) = s.replaceAll("""\$line\d+[./]\$(read|eval|print)[$.]""", "")
12691268
private def removeIWPackages(s: String) = s.replaceAll("""\$(iw|read|eval|print)[$.]""", "")
1269+
@deprecated("Use intp.naming.unmangle.", "2.12.0-M5")
12701270
def stripString(s: String) = removeIWPackages(removeLineWrapper(s))
12711271

12721272
private[interpreter] def withSuppressedSettings[A](settings: Settings, global: => Global)(body: => A): A = {

0 commit comments

Comments
 (0)