Skip to content
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ trait ScalaSettings extends AbsScalaSettings
val YmacroFresh = BooleanSetting ("-Ymacro-global-fresh-names", "Should fresh names in macros be unique across all compilation units")
val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup")
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects")
val YreplMagicImport = BooleanSetting ("-Yrepl-use-magic-imports", "In the code the wraps REPL snippes, use magic imports to rather than nesting wrapper object/classes")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry to snipe at snippes, which I guess is the French.

In the code that wraps REPL snippets, use magic imports rather

val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "")
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.")
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
Expand Down
47 changes: 30 additions & 17 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ trait Contexts { self: Analyzer =>
object NoContext
extends Context(EmptyTree, NoSymbol, EmptyScope, NoCompilationUnit,
// We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer`
null) {
null, 0) {
enclClass = this
enclMethod = this

override val depth = 0
override def nextEnclosing(p: Context => Boolean): Context = this
override def enclosingContextChain: List[Context] = Nil
override def implicitss: List[List[ImplicitInfo]] = Nil
Expand Down Expand Up @@ -191,7 +190,7 @@ trait Contexts { self: Analyzer =>
* @param _outer The next outer context.
*/
class Context private[typechecker](val tree: Tree, val owner: Symbol, val scope: Scope,
val unit: CompilationUnit, _outer: Context,
val unit: CompilationUnit, _outer: Context, val depth: Int,
private[this] var _reporter: ContextReporter = new ThrowingReporter) {
private def outerIsNoContext = _outer eq null
final def outer: Context = if (outerIsNoContext) NoContext else _outer
Expand Down Expand Up @@ -237,16 +236,12 @@ trait Contexts { self: Analyzer =>

protected def outerDepth = if (outerIsNoContext) 0 else outer.depth

val depth: Int = {
val increasesDepth = isRootImport || outerIsNoContext || (outer.scope != scope)
( if (increasesDepth) 1 else 0 ) + outerDepth
}

/** The currently visible imports */
/** The currently visible imports, from innermost to outermost. */
def imports: List[ImportInfo] = outer.imports
/** Equivalent to `imports.headOption`, but more efficient */
def firstImport: Option[ImportInfo] = outer.firstImport
protected[Contexts] def importOrNull: ImportInfo = null
/** A root import is never unused and always bumps context depth. (e.g scala._ / Predef._ and magic REPL imports) */
def isRootImport: Boolean = false

/** Types for which implicit arguments are currently searched */
Expand Down Expand Up @@ -488,11 +483,17 @@ trait Contexts { self: Analyzer =>
else if (!sameOwner && owner.isTerm) NoPrefix
else prefix

def innerDepth(isRootImport: Boolean) = {
val increasesDepth = isRootImport || (this == NoContext) || (this.scope != scope)
depth + (if (increasesDepth) 1 else 0)
}

// The blank canvas
val c = if (isImport)
new Context(tree, owner, scope, unit, this, reporter) with ImportContext
else
new Context(tree, owner, scope, unit, this, reporter)
val c = if (isImport) {
val isRootImport = !tree.pos.isDefined || isReplImportWrapperImport(tree)
new ImportContext(tree, owner, scope, unit, this, isRootImport, innerDepth(isRootImport), reporter)
} else
new Context(tree, owner, scope, unit, this, innerDepth(isRootImport = false), reporter)

// Fields that are directly propagated
c.variance = variance
Expand Down Expand Up @@ -1100,6 +1101,16 @@ trait Contexts { self: Analyzer =>
}
}


private def isReplImportWrapperImport(tree: Tree): Boolean = {
tree match {
case Import(expr, selector :: Nil) =>
// Just a syntactic check to avoid forcing typechecking of imports
selector.name.string_==(nme.INTERPRETER_IMPORT_LEVEL_UP) && owner.enclosingTopLevelClass.isInterpreterWrapper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My other use case was to submit a directory of source files as a single compilation unit, with files wrapped in LEVEL_UP and LEVEL_DOWN and then concatenated in a single source, so that top-level statements would not collide. That would not be in a repl wrapper. If double brace isn't magical enough, how about ${{?

Maybe also call it WRAPL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the user-level use case for that? Something to do with sealed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, on that thread they were complaining that their files get too big when they cram everything in. That also covers companionship. Sometimes I feel like I'm crammed in with my companion.

case _ => false
}
}

} //class Context

/** Find the symbol of a simple name starting from this context.
Expand Down Expand Up @@ -1307,8 +1318,11 @@ trait Contexts { self: Analyzer =>
}

/** A `Context` focussed on an `Import` tree */
trait ImportContext extends Context {
private val impInfo: ImportInfo = {
final class ImportContext(tree: Tree, owner: Symbol, scope: Scope,
unit: CompilationUnit, outer: Context,
override val isRootImport: Boolean, depth: Int,
reporter: ContextReporter) extends Context(tree, owner, scope, unit, outer, depth, reporter) {
private[this] val impInfo: ImportInfo = {
val info = new ImportInfo(tree.asInstanceOf[Import], outerDepth)
if (settings.warnUnusedImport && openMacros.isEmpty && !isRootImport) // excludes java.lang/scala/Predef imports
allImportInfos(unit) ::= info
Expand All @@ -1317,7 +1331,7 @@ trait Contexts { self: Analyzer =>
override final def imports = impInfo :: super.imports
override final def firstImport = Some(impInfo)
override final def importOrNull = impInfo
override final def isRootImport = !tree.pos.isDefined

override final def toString = s"${super.toString} with ImportContext { $impInfo; outer.owner = ${outer.owner} }"
}

Expand Down Expand Up @@ -1467,7 +1481,6 @@ trait Contexts { self: Analyzer =>
protected def handleError(pos: Position, msg: String): Unit = reporter.error(pos, msg)
}


private[typechecker] class BufferingReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[(Position, String)] = null) extends ContextReporter(_errorBuffer, _warningBuffer) {
override def isBuffering = true

Expand Down
7 changes: 4 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,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 = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um. I guess since we're talking import magic. I know the check used to be wrong, false positive. Now I'm not sure what the user story is. Oh, this is covered by unused import. So is this check only guarding against old bugs in name binding? Including implicit shadowing?

object X {
  val global = 0
  import concurrent.ExecutionContext.global
  println(global)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I see bug 2866 is due to Jason: "I have five unanswered questions"... after grepping for permanently hidden again in the check files...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I dropped your changes to this code from #5220 as I didn't understand the original code or the change. 🤷‍♂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's very meaningful, even if it had a bug-compatible meaning once.

val e = context.scope.lookupEntry(to)

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


/** Given a case class
* case class C[Ts] (ps: Us)
* Add the following methods to toScope:
Expand Down
27 changes: 26 additions & 1 deletion src/partest-extras/scala/tools/partest/ReplTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
package scala.tools.partest

import scala.tools.nsc.Settings
import scala.tools.nsc.interpreter.{ ILoop, replProps }
import scala.tools.nsc.interpreter.{ILoop, replProps}
import scala.util.matching.Regex
import scala.util.matching.Regex.Match

/** A class for testing repl code.
Expand Down Expand Up @@ -67,6 +68,30 @@ 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) = {
stripIdentityHashCode(super.normalize(s))
}
}
object Hashless {
private val hashless = "@[a-fA-F0-9]+".r
private def stripIdentityHashCode(s: String): String = hashless.replaceAllIn(s, "@XXXXXXXX")
}

/** Strip dynamic parts of LambdaMetafactory synthetic class names. */
trait Lambdaless extends ReplTest {
import Lambdaless._
override def normalize(s: String) = {
stripLambdaClassName(super.normalize(s))
}
}
object Lambdaless {
private val lambdaless = """\$Lambda\$\d+/\d+(@[a-fA-F0-9]+)?""".r
private def stripLambdaClassName(s: String): String = lambdaless.replaceAllIn(s, Regex.quoteReplacement("<function>"))
}

/** Run a REPL test from a session transcript.
* The `session` is read from the `.check` file.
*/
Expand Down
4 changes: 3 additions & 1 deletion src/reflect/scala/reflect/internal/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ trait StdNames {
val EXCEPTION_RESULT_PREFIX = "exceptionResult"
val EXPAND_SEPARATOR_STRING = "$$"
val FRESH_TERM_NAME_PREFIX = "x$"
val INTERPRETER_IMPORT_LEVEL_UP = NameTransformer.encode("{{")
val INTERPRETER_IMPORT_WRAPPER = "$iw"
val INTERPRETER_WRAPPER = "$read"
val LOCALDUMMY_PREFIX = "<local " // owner of local blocks
val PROTECTED_PREFIX = "protected$"
val PROTECTED_SET_PREFIX = PROTECTED_PREFIX + "set"
Expand Down Expand Up @@ -392,7 +394,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) || (name containsName INTERPRETER_IMPORT_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: 3 additions & 7 deletions src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -706,11 +706,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
final def isOverridableMember = !(isClass || isEffectivelyFinal || isTypeParameter) && safeOwner.isClass

/** Does this symbol denote a wrapper created by the repl? */
final def isInterpreterWrapper = (
(this hasFlag MODULE)
&& isTopLevel
&& nme.isReplWrapperName(name)
)
final def isInterpreterWrapper = 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 @@ -874,7 +870,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.$iw.Foo and <empty>.Bippy.
*/
final def isOmittablePrefix = /*!settings.debug.value &&*/ {
// scala/bug#5941 runtime reflection can have distinct symbols representing `package scala` (from different mirrors)
Expand All @@ -886,7 +882,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.$iw.Foo or $read.INSTANCE.$iw.Foo
)
def isFBounded = info match {
case TypeBounds(_, _) => info.baseTypeSeq exists (_ contains this)
Expand Down
44 changes: 23 additions & 21 deletions src/repl/scala/tools/nsc/interpreter/IMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ package scala
package tools.nsc
package interpreter

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

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

private[nsc] var printResults = true // whether to print result lines
private[nsc] var totalSilence = false // whether to print anything
Expand Down Expand Up @@ -318,15 +318,16 @@ 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 = {
if (isClassBased) {
val readName = java.util.regex.Matcher.quoteReplacement(sessionNames.read)
p.replaceFirst(readName, readName + readInstanceName)
} else p
if (isClassBased) p.replace(sessionNames.read, sessionNames.read + readInstanceName) else p
}
def flatPath(sym: Symbol): String = {
val sym1 = if (sym.isModule) sym.moduleClass else sym
flatOp shift sym1.javaClassName
}
def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName

def translatePath(path: String) = {
val sym = if (path endsWith "$") symbolOfTerm(path.init) else symbolOfIdent(path)
Expand All @@ -335,7 +336,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$iw$X.class.
*/
def translateSimpleResource(path: String): Option[String] = {
if (!(path contains '/') && (path endsWith ".class")) {
Expand Down Expand Up @@ -711,11 +712,10 @@ 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
}
// Example input: $line3.$read$$iw$
val classNameRegex = naming.lineRegex
def isWrapperInit(x: StackTraceElement) =
x.getMethodName == nme.CONSTRUCTOR.decoded && classNameRegex.pattern.matcher(x.getClassName).find()
val stackTrace = unwrapped stackTracePrefixString (!isWrapperInit(_))

withLastExceptionLock[String]({
Expand Down Expand Up @@ -863,7 +863,6 @@ 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 envLines = {
if (!isReplPower) Nil // power mode only for now
else {
Expand All @@ -884,6 +883,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
/** A format string with %s for $read, specifying the wrapper definition. */
def preambleHeader: String

def postamble: String

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

Expand Down Expand Up @@ -990,11 +991,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
}

// 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 @@ -1007,7 +1004,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 @@ -1060,7 +1060,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
def typeOfTerm(id: String): Type = symbolOfTerm(id).tpe

// Given the fullName of the symbol, reflectively drill down the path
def valueOfTerm(id: String): Option[Any] = {
def valueOfTerm(id: String): Option[Any] = exitingTyper {
def value(fullName: String) = {
val mirror = runtimeMirror
import mirror.universe.{Symbol, InstanceMirror, TermName}
Expand Down Expand Up @@ -1088,7 +1088,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
mirror.reflect(mirrored.reflectMethod(s.asMethod).apply())
}
else {
assert(false, originalPath(s))
assert(false, fullName)
inst
}
loop(i, s, rest)
Expand Down Expand Up @@ -1142,6 +1142,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
)
)
}
// this is harder than getting the typed trees and fixing up the string to emit that reports types
def cleanMemberDecl(owner: Symbol, member: Name): Type =
cleanTypeAfterTyper(owner.info nonPrivateDecl member)

Expand Down Expand Up @@ -1267,6 +1268,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))

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