Skip to content

Commit

Permalink
repl power mode improvements.
Browse files Browse the repository at this point in the history
Implemented great suggestion from moors. More imports in power mode,
including the contents of treedsl. Also, another swing at overcoming
the mismatched global singletons problem, this time taking advantage of
dependent method types. Amazingly, it seems to work.

Continuing in the quest to create a useful compiler hacking environment,
there is now an implicit from Symbol which allows you to pretend a
Symbol takes type parameters, and the result is the applied type based
on the manifests of the type arguments and the type constructor of the
symbol. Examples:

    // magic with manifests
    scala> val tp = ArrayClass[scala.util.Random]
    tp: $r.global.Type = Array[scala.util.Random]

    // evidence
    scala> tp.memberType(Array_apply)
    res0: $r.global.Type = (i: Int)scala.util.Random

    // treedsl
    scala> val m = LIT(10) MATCH (CASE(LIT(5)) ==> FALSE, DEFAULT ==> TRUE)
    m: $r.treedsl.global.Match =
    10 match {
      case 5 => false
      case _ => true
    }

    // typed is in scope
    scala> typed(m).tpe
    res1: $r.treedsl.global.Type = Boolean
  • Loading branch information
paulp committed Dec 28, 2011
1 parent 33ab1a5 commit d05881e
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 98 deletions.
24 changes: 24 additions & 0 deletions src/compiler/scala/reflect/internal/Definitions.scala
Expand Up @@ -386,6 +386,30 @@ trait Definitions extends reflect.api.StandardDefinitions {
lazy val NoneModule: Symbol = getModule("scala.None")
lazy val SomeModule: Symbol = getModule("scala.Some")

/** Note: don't use this manifest/type function for anything important,
* as it is incomplete. Would love to have things like existential types
* working, but very unfortunately the manifests just stuff the relevant
* information into the toString method.
*/
def manifestToType(m: OptManifest[_]): Type = m match {
case x: AnyValManifest[_] =>
getClassIfDefined("scala." + x).tpe
case m: ClassManifest[_] =>
val name = m.erasure.getName
if (name endsWith nme.MODULE_SUFFIX_STRING)
getModuleIfDefined(name stripSuffix nme.MODULE_SUFFIX_STRING).tpe
else {
val sym = getClassIfDefined(name)
val args = m.typeArguments

if (sym eq NoSymbol) NoType
else if (args.isEmpty) sym.tpe
else appliedType(sym.typeConstructor, args map manifestToType)
}
case _ =>
NoType
}

// The given symbol represents either String.+ or StringAdd.+
def isStringAddition(sym: Symbol) = sym == String_+ || sym == StringAdd_+
def isArrowAssoc(sym: Symbol) = ArrowAssocClass.tpe.decls.toList contains sym
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/scala/reflect/internal/Types.scala
Expand Up @@ -5487,6 +5487,10 @@ A type's typeSymbol should never be inspected directly.
case _ =>
t
}
def elimRefinement(t: Type) = t match {
case RefinedType(parents, decls) if !decls.isEmpty => intersectionType(parents)
case _ => t
}

/** A collector that tests for existential types appearing at given variance in a type */
class ContainsVariantExistentialCollector(v: Int) extends TypeCollector(false) {
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/scala/tools/nsc/interpreter/ILoop.scala
Expand Up @@ -51,7 +51,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
intp.reporter.printMessage(msg)

def isAsync = !settings.Yreplsync.value
lazy val power = Power(this)
lazy val power = new Power(intp, new StdReplVals(this))

// TODO
// object opt extends AestheticSettings
Expand Down Expand Up @@ -253,6 +253,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
/** Power user commands */
lazy val powerCommands: List[LoopCommand] = List(
nullary("dump", "displays a view of the interpreter's internal state", dumpCommand),
nullary("vals", "gives information about the power mode repl vals", valsCommand),
cmd("phase", "<phase>", "set the implicit phase for power commands", phaseCommand),
cmd("wrap", "<method>", "name of method to wrap around each repl line", wrapCommand) withLongHelp ("""
|:wrap
Expand Down Expand Up @@ -283,6 +284,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
history.asStrings takeRight 30 foreach echo
in.redrawLine()
}
private def valsCommand(): Result = power.valsDescription

private val typeTransforms = List(
"scala.collection.immutable." -> "immutable.",
Expand Down
111 changes: 29 additions & 82 deletions src/compiler/scala/tools/nsc/interpreter/Power.scala
Expand Up @@ -15,54 +15,12 @@ import scala.io.Codec
import java.net.{ URL, MalformedURLException }
import io.{ Path }

trait SharesGlobal {
type GlobalType <: Global
val global: GlobalType

// This business gets really old:
//
// found : power.intp.global.Symbol
// required: global.Symbol
//
// Have tried many ways to cast it aside, this is the current winner.
// Todo: figure out a way to abstract over all the type members.
type AnySymbol = Global#Symbol
type AnyType = Global#Type
type AnyName = Global#Name
type AnyTree = Global#Tree

type Symbol = global.Symbol
type Type = global.Type
type Name = global.Name
type Tree = global.Tree

implicit def upDependentSymbol(x: AnySymbol): Symbol = x.asInstanceOf[Symbol]
implicit def upDependentType(x: AnyType): Type = x.asInstanceOf[Type]
implicit def upDependentName(x: AnyName): Name = x.asInstanceOf[Name]
implicit def upDependentTree(x: AnyTree): Tree = x.asInstanceOf[Tree]
}

object Power {
def apply(intp: IMain): Power = apply(null, intp)
def apply(repl: ILoop): Power = apply(repl, repl.intp)
def apply(repl: ILoop, intp: IMain): Power =
new Power(repl, intp) {
type GlobalType = intp.global.type
final val global: intp.global.type = intp.global
}
}

/** A class for methods to be injected into the intp in power mode.
*/
abstract class Power(
val repl: ILoop,
val intp: IMain
) extends SharesGlobal {
import intp.{
beQuietDuring, typeOfExpression, interpret, parse
}
import global._
import definitions.{ getClassIfDefined, getModuleIfDefined }
class Power[ReplValsImpl <: ReplVals : Manifest](val intp: IMain, replVals: ReplValsImpl) {
import intp.{ beQuietDuring, typeOfExpression, interpret, parse }
import intp.global._
import definitions.{ manifestToType, getClassIfDefined, getModuleIfDefined }

abstract class SymSlurper {
def isKeep(sym: Symbol): Boolean
Expand Down Expand Up @@ -130,20 +88,21 @@ abstract class Power(
private def customInit = replProps.powerInitCode.option flatMap (f => io.File(f).safeSlurp())

def banner = customBanner getOrElse """
|** Power User mode enabled - BEEP BOOP SPIZ **
|** Power User mode enabled - BEEP WHIR GYVE **
|** :phase has been set to 'typer'. **
|** scala.tools.nsc._ has been imported **
|** global._ and definitions._ also imported **
|** Try :help, vals.<tab>, power.<tab> **
|** global._, definitions._ also imported **
|** Try :help, :vals, power.<tab> **
""".stripMargin.trim

private def initImports = List(
"scala.tools.nsc._",
"scala.collection.JavaConverters._",
"intp.global.{ error => _, _ }",
"definitions.{ getClass => _, _ }",
"power.Implicits._",
"power.rutil._"
"power.rutil._",
"replImplicits._",
"treedsl.CODE._"
)

def init = customInit match {
Expand All @@ -155,12 +114,23 @@ abstract class Power(
*/
def unleash(): Unit = beQuietDuring {
// First we create the ReplVals instance and bind it to $r
intp.bind("$r", new ReplVals(repl))
intp.bind("$r", replVals)
// Then we import everything from $r.
intp interpret ("import " + intp.pathToTerm("$r") + "._")
// And whatever else there is to do.
init.lines foreach (intp interpret _)
}
def valsDescription: String = {
def to_str(m: Symbol) = "%12s %s".format(
m.decodedName, "" + elimRefinement(m.accessedOrSelf.tpe) stripPrefix "scala.tools.nsc.")

( rutil.info[ReplValsImpl].declares
filter (m => m.isPublic && !m.hasModuleFlag && !m.isConstructor)
sortBy (_.decodedName)
map to_str
mkString ("Name and type of values imported into the repl in power mode.\n\n", "\n", "")
)
}

trait LowPriorityInternalInfo {
implicit def apply[T: Manifest] : InternalInfo[T] = new InternalInfo[T](None)
Expand All @@ -180,25 +150,6 @@ abstract class Power(
private def symbol = symbol_
private def name = name_

// Would love to have stuff like existential types working,
// but very unfortunately those manifests just stuff the relevant
// information into the toString method. Boo.
private def manifestToType(m: Manifest[_]): Type = m match {
case x: AnyValManifest[_] =>
getClassIfDefined("scala." + x).tpe
case _ =>
val name = m.erasure.getName
if (name endsWith nme.MODULE_SUFFIX_STRING)
getModuleIfDefined(name stripSuffix nme.MODULE_SUFFIX_STRING).tpe
else {
val sym = getClassIfDefined(name)
val args = m.typeArguments

if (args.isEmpty) sym.tpe
else typeRef(NoPrefix, sym, args map manifestToType)
}
}

def symbol_ : Symbol = getClassIfDefined(erasure.getName)
def tpe_ : Type = manifestToType(man)
def name_ : Name = symbol.name
Expand All @@ -208,9 +159,10 @@ abstract class Power(
def owner = symbol.owner
def owners = symbol.ownerChain drop 1
def defn = symbol.defString
def decls = symbol.info.decls

def declares = members filter (_.owner == symbol)
def inherits = members filterNot (_.owner == symbol)
def declares = decls.toList
def inherits = members filterNot (declares contains _)
def types = members filter (_.name.isTypeName)
def methods = members filter (_.isMethod)
def overrides = declares filter (_.isOverride)
Expand All @@ -233,8 +185,8 @@ abstract class Power(

def whoHas(name: String) = bts filter (_.decls exists (_.name.toString == name))
def <:<[U: Manifest](other: U) = tpe <:< InternalInfo[U].tpe
def lub[U: Manifest](other: U) = global.lub(List(tpe, InternalInfo[U].tpe))
def glb[U: Manifest](other: U) = global.glb(List(tpe, InternalInfo[U].tpe))
def lub[U: Manifest](other: U) = intp.global.lub(List(tpe, InternalInfo[U].tpe))
def glb[U: Manifest](other: U) = intp.global.glb(List(tpe, InternalInfo[U].tpe))

def shortClass = erasure.getName split "[$.]" last
override def toString = value match {
Expand Down Expand Up @@ -337,7 +289,7 @@ abstract class Power(
def pp() { intp prettyPrint slurp() }
}

protected trait Implicits1 {
trait Implicits1 {
// fallback
implicit def replPrinting[T](x: T)(implicit pretty: Prettifier[T] = Prettifier.default[T]) =
new SinglePrettifierClass[T](x)
Expand Down Expand Up @@ -367,7 +319,6 @@ abstract class Power(
implicit def replInputStream(in: InputStream)(implicit codec: Codec) = new RichInputStream(in)
implicit def replEnhancedURLs(url: URL)(implicit codec: Codec): RichReplURL = new RichReplURL(url)(codec)
}
object Implicits extends Implicits2 { }

trait ReplUtilities {
def module[T: Manifest] = getModuleIfDefined(manifest[T].erasure.getName stripSuffix nme.MODULE_SUFFIX_STRING)
Expand Down Expand Up @@ -396,11 +347,7 @@ abstract class Power(
}

lazy val rutil: ReplUtilities = new ReplUtilities { }

lazy val phased: Phased = new Phased with SharesGlobal {
type GlobalType = Power.this.global.type
final val global: Power.this.global.type = Power.this.global
}
lazy val phased: Phased = new { val global: intp.global.type = intp.global } with Phased { }

def context(code: String) = analyzer.rootContext(unit(code))
def source(code: String) = new BatchSourceFile("<console>", code)
Expand Down
75 changes: 64 additions & 11 deletions src/compiler/scala/tools/nsc/interpreter/ReplVals.scala
Expand Up @@ -6,15 +6,68 @@
package scala.tools.nsc
package interpreter

final class ReplVals(r: ILoop) {
lazy val repl = r
lazy val intp = r.intp
lazy val power = r.power
lazy val reader = r.in
lazy val vals = this
lazy val global = intp.global
lazy val isettings = intp.isettings
lazy val completion = reader.completion
lazy val history = reader.history
lazy val phased = power.phased
/** A class which the repl utilizes to expose predefined objects.
* The base implementation is empty; the standard repl implementation
* is StdReplVals.
*/
abstract class ReplVals { }

class StdReplVals(final val r: ILoop) extends ReplVals {
final lazy val repl = r
final lazy val intp = r.intp
final lazy val power = r.power
final lazy val reader = r.in
final lazy val vals = this
final lazy val global: intp.global.type = intp.global
final lazy val isettings = intp.isettings
final lazy val completion = reader.completion
final lazy val history = reader.history
final lazy val phased = power.phased
final lazy val analyzer = global.analyzer

final lazy val treedsl = new { val global: intp.global.type = intp.global } with ast.TreeDSL { }
final lazy val typer = analyzer.newTyper(
analyzer.rootContext(
power.unit("").asInstanceOf[analyzer.global.CompilationUnit]
)
)

final lazy val replImplicits = new power.Implicits2 {
import intp.global._

private val manifestFn = ReplVals.mkManifestToType[intp.global.type](global)
implicit def mkManifestToType(sym: Symbol) = manifestFn(sym)
}

def typed[T <: analyzer.global.Tree](tree: T): T = typer.typed(tree).asInstanceOf[T]
}

object ReplVals {
/** Latest attempt to work around the challenge of foo.global.Type
* not being seen as the same type as bar.global.Type even though
* the globals are the same. Dependent method types to the rescue.
*/
def mkManifestToType[T <: Global](global: T) = {
import global._
import definitions._

/** We can't use definitions.manifestToType directly because we're passing
* it to map and the compiler refuses to perform eta expansion on a method
* with a dependent return type. (Can this be relaxed?) To get around this
* I have this forwarder which widens the type and then cast the result back
* to the dependent type.
*/
def manifestToType(m: OptManifest[_]): Global#Type =
definitions.manifestToType(m)

class AppliedTypeFromManifests(sym: Symbol) {
def apply[M](implicit m1: Manifest[M]): Type =
appliedType(sym.typeConstructor, List(m1) map (x => manifestToType(x).asInstanceOf[Type]))

def apply[M1, M2](implicit m1: Manifest[M1], m2: Manifest[M2]): Type =
appliedType(sym.typeConstructor, List(m1, m2) map (x => manifestToType(x).asInstanceOf[Type]))
}

(sym: Symbol) => new AppliedTypeFromManifests(sym)
}
}
3 changes: 2 additions & 1 deletion src/compiler/scala/tools/reflect/Mock.scala
Expand Up @@ -25,7 +25,8 @@ trait Mock extends (Invoked => AnyRef) {

def newInvocationHandler() = new InvocationHandler {
def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]) =
mock(Invoked(proxy, method, args))
try { mock(Invoked(proxy, method, args)) }
catch { case _: NoClassDefFoundError => sys.exit(1) }
}
}

Expand Down
22 changes: 19 additions & 3 deletions test/files/run/repl-power.check
Expand Up @@ -2,15 +2,31 @@ Type in expressions to have them evaluated.
Type :help for more information.

scala> :power
** Power User mode enabled - BEEP BOOP SPIZ **
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'. **
** scala.tools.nsc._ has been imported **
** global._ and definitions._ also imported **
** Try :help, vals.<tab>, power.<tab> **
** global._, definitions._ also imported **
** Try :help, :vals, power.<tab> **

scala> // guarding against "error: reference to global is ambiguous"

scala> global.emptyValDef // "it is imported twice in the same scope by ..."
res0: $r.global.emptyValDef.type = private val _ = _

scala> val tp = ArrayClass[scala.util.Random] // magic with manifests
tp: $r.global.Type = Array[scala.util.Random]

scala> tp.memberType(Array_apply) // evidence
res1: $r.global.Type = (i: Int)scala.util.Random

scala> val m = LIT(10) MATCH (CASE(LIT(5)) ==> FALSE, DEFAULT ==> TRUE) // treedsl
m: $r.treedsl.global.Match =
10 match {
case 5 => false
case _ => true
}

scala> typed(m).tpe // typed is in scope
res2: $r.treedsl.global.Type = Boolean

scala>
4 changes: 4 additions & 0 deletions test/files/run/repl-power.scala
Expand Up @@ -5,6 +5,10 @@ object Test extends ReplTest {
:power
// guarding against "error: reference to global is ambiguous"
global.emptyValDef // "it is imported twice in the same scope by ..."
val tp = ArrayClass[scala.util.Random] // magic with manifests
tp.memberType(Array_apply) // evidence
val m = LIT(10) MATCH (CASE(LIT(5)) ==> FALSE, DEFAULT ==> TRUE) // treedsl
typed(m).tpe // typed is in scope
""".trim
}

0 comments on commit d05881e

Please sign in to comment.