Skip to content

Commit

Permalink
improve import handling for renaming-imports and back-ticked identifi…
Browse files Browse the repository at this point in the history
…ers; tests still fail due to renaming an import to multiple names still failing
  • Loading branch information
Li Haoyi committed Mar 10, 2015
1 parent 47a361e commit a576aa4
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 58 deletions.
5 changes: 4 additions & 1 deletion repl/src/main/scala/ammonite/repl/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ case class Catching(handler: PartialFunction[Throwable, Res.Failing]) {
case class Evaluated(wrapper: String,
imports: Seq[ImportData])

case class ImportData(imported: String, wrapperName: String, prefix: String)
case class ImportData(fromName: String,
toName: Option[String],
wrapperName: String,
prefix: String)

/**
* Encapsulates a read-write cell that can be passed around
Expand Down
4 changes: 4 additions & 0 deletions repl/src/main/scala/ammonite/repl/frontend/ReplAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ trait Load extends (String => Unit){
abstract class FullReplAPI extends ReplAPI{
def shellPPrint[T: WeakTypeTag](value: => T, ident: String): String
def shellPrintDef(definitionLabel: String, ident: String): String
def shellPrintImport(imported: String): String
def typeOf[T: WeakTypeTag] = scala.reflect.runtime.universe.weakTypeOf[T]
def typeOf[T: WeakTypeTag](t: => T) = scala.reflect.runtime.universe.weakTypeOf[T]
}
Expand Down Expand Up @@ -135,4 +136,7 @@ trait DefaultReplAPI extends FullReplAPI {
def shellPrintDef(definitionLabel: String, ident: String) = {
s"defined ${colors.`type`}$definitionLabel ${colors.ident}$ident${colors.reset}"
}
def shellPrintImport(imported: String) = {
s"${colors.`type`}import ${colors.ident}$imported${colors.reset}"
}
}
44 changes: 27 additions & 17 deletions repl/src/main/scala/ammonite/repl/interp/AmmonitePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,54 @@ class AmmonitePlugin(g: scala.tools.nsc.Global, output: Seq[ImportData] => Unit)
val phaseName = "AmmonitePhase"
def newPhase(prev: Phase): Phase = new g.GlobalPhase(prev) {
def name = phaseName
def decode(t: g.Tree) = (t.symbol, t.symbol.decodedName, None, "")
def apply(unit: g.CompilationUnit): Unit = {
val stats = unit.body.children.last.asInstanceOf[g.ModuleDef].impl.body
val symbols = stats.foldLeft(List.empty[(g.Symbol, String)]){
val symbols = stats.foldLeft(List.empty[(g.Symbol, String, Option[String], String)]){
// These are all the ways we want to import names from previous
// executions into the current one. Most are straightforward, except
// `import` statements for which we make use of the typechecker to
// resolve the imported names
case (ctx, t @ g.Import(expr, _)) =>
val syms = new g.analyzer.ImportInfo(t, 0).allImportedSymbols
case (ctx, t @ g.Import(expr, selectors)) =>
def rec(expr: g.Tree): List[g.Name] = {
expr match {
case g.Select(lhs, name) => name :: rec(lhs)
case g.Ident(name) => List(name)
}
}
val prefix = rec(expr).reverse
.map(x => BacktickWrap(x.decoded))
.mkString(".")
.map(x => BacktickWrap(x.decoded))
.mkString(".")

syms.filter(_.isPublic).map(_ -> prefix).toList ::: ctx
case (ctx, t @ g.DefDef(_, _, _, _, _, _)) => (t.symbol, "") :: ctx
case (ctx, t @ g.ValDef(_, _, _, _)) => (t.symbol, "") :: ctx
case (ctx, t @ g.ClassDef(_, _, _, _)) => (t.symbol, "") :: ctx
case (ctx, t @ g.ModuleDef(_, _, _)) => (t.symbol, "") :: ctx
case (ctx, t @ g.TypeDef(_, _, _, _)) => (t.symbol, "") :: ctx
val renamings =
for(t @ g.ImportSelector(name, _, rename, _) <- selectors) yield {
Option(rename).map(name.decoded -> _.decoded)
}
val renameMap = renamings.flatten.map(_.swap).toMap
val info = new g.analyzer.ImportInfo(t, 0)
val syms = info.allImportedSymbols
.filter(_.isPublic)
.map(s => (s, renameMap.getOrElse(s.decodedName, s.decodedName), Some(s.decodedName), prefix))
.toList
syms ::: ctx
case (ctx, t @ g.DefDef(_, _, _, _, _, _)) => decode(t) :: ctx
case (ctx, t @ g.ValDef(_, _, _, _)) => decode(t) :: ctx
case (ctx, t @ g.ClassDef(_, _, _, _)) => decode(t) :: ctx
case (ctx, t @ g.ModuleDef(_, _, _)) => decode(t) :: ctx
case (ctx, t @ g.TypeDef(_, _, _, _)) => decode(t) :: ctx
case (ctx, _) => ctx
}

output(
for {
(sym, importString) <- symbols
(sym, fromName, toName, importString) <- symbols
if !sym.isSynthetic
if !sym.isPrivate
name = sym.decodedName
if name != "<init>"
if name != "<clinit>"
if name != "$main"
} yield ImportData(name, "", importString)

if fromName != "<init>"
if fromName != "<clinit>"
if fromName != "$main"
} yield ImportData(fromName, toName, "", importString)
)
}
}
Expand Down
19 changes: 13 additions & 6 deletions repl/src/main/scala/ammonite/repl/interp/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ object Evaluator{
* errors instead of the desired shadowing.
*/
lazy val previousImports = mutable.Map(
namesFor(typeOf[ReplAPI]).map(n => n -> ImportData(n, "", "ReplBridge.shell")).toSeq ++
namesFor(typeOf[ammonite.repl.IvyConstructor]).map(n => n -> ImportData(n, "", "ammonite.repl.IvyConstructor")).toSeq
namesFor(typeOf[ReplAPI]).map(n => n -> ImportData(n, None, "", "ReplBridge.shell")).toSeq ++
namesFor(typeOf[ammonite.repl.IvyConstructor]).map(n => n -> ImportData(n, None, "", "ammonite.repl.IvyConstructor")).toSeq
:_*
)

Expand Down Expand Up @@ -153,10 +153,17 @@ object Evaluator{
.values
.groupBy(_.prefix)
.map{
case (prefix, Seq(imp)) =>
s"import $prefix.${BacktickWrap(imp.imported)}"
case (prefix, Seq(imp)) if imp.fromName == imp.toName=>
s"import $prefix.${BacktickWrap(imp.fromName)}"
case (prefix, imports) =>
s"import $prefix.{${imports.map(x => "\n " + BacktickWrap(x.imported)).mkString(",")}\n}"
val lines = for(x <- imports) yield {
x.toName match{
case None => "\n " + BacktickWrap(x.fromName)
case Some(toName) => "\n " + BacktickWrap(x.fromName) + " => " + BacktickWrap(toName)
}
}
val block = lines.mkString(",")
s"import $prefix.{$block\n}"
}
.mkString("\n")
}
Expand Down Expand Up @@ -203,7 +210,7 @@ object Evaluator{
}

def update(newImports: Seq[ImportData]) = {
for(i <- newImports) previousImports(i.imported) = i
for(i <- newImports) previousImports(i.toName.getOrElse(i.fromName)) = i
}
}

Expand Down
4 changes: 3 additions & 1 deletion repl/src/main/scala/ammonite/repl/interp/Preprocessor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ object Preprocessor{
}

val Import = Processor{
case (name, code, tree: G#Import) => Output(code, Seq(s"""Iterator("$code")"""))
case (name, code, tree: G#Import) =>
val Array(keyword, body) = code.split(" ", 2)
Output(code, Seq(s"""Iterator(ReplBridge.shell.shellPrintImport("$body"))"""))
}

val Expr = Processor{
Expand Down
104 changes: 71 additions & 33 deletions repl/src/test/scala/ammonite/repl/EvaluatorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,53 +131,83 @@ object EvaluatorTests extends TestSuite{
""")
}

'import0 {
check.session("""
@ import math.abs
import math.abs
'import{

@ val abs = 123L
abs: Long = 123L
'basic {
check.session("""
@ import math.abs
import math.abs
@ abs
res2: Long = 123L
""")
}
@ val abs = 123L
abs: Long = 123L
'import{
check.session("""
@ val abs = 'a'
abs: Char = 'a'
@ abs
res2: Long = 123L
""")
}
'shadowing{
check.session("""
@ val abs = 'a'
abs: Char = 'a'
@ abs
res1: Char = 'a'
@ abs
res1: Char = 'a'
@ val abs = 123L
abs: Long = 123L
@ val abs = 123L
abs: Long = 123L
@ abs
res3: Long = 123L
@ abs
res3: Long = 123L
@ import math.abs
import math.abs
@ import math.abs
import math.abs
@ abs(-10)
res5: Int = 10
@ abs(-10)
res5: Int = 10
@ val abs = 123L
abs: Long = 123L
@ val abs = 123L
abs: Long = 123L
@ abs
res7: Long = 123L
@ abs
res7: Long = 123L
@ import java.lang.Math._
import java.lang.Math._
@ import java.lang.Math._
import java.lang.Math._
@ abs(-4)
res9: Int = 4
""")
@ abs(-4)
res9: Int = 4
""")
}
'renaming{
check.session("""
@ import math.{abs => sba}
@ sba(-123)
res1: Int = 123
@ abs
error: not found: value abs
@ import math.{abs, max => xam}
@ abs(-234)
res4: Int = 234
@ xam(1, 2)
res5: Int = 2
@ import math.{_, min => _}
@ max(2, 3)
res7: Int = 3
@min
error: not found: value min
""")
}
}


'classes{
check.session("""
@ class C{override def toString() = "Ceee"}
Expand Down Expand Up @@ -329,6 +359,14 @@ object EvaluatorTests extends TestSuite{
@ ` `
res7: Int = 333
@ object ` `{ val ` ` = 123};
defined object ` `
@ import ` `.{` ` => `l o l`}
@ `l o l`
res10: Int = 123
""")
}
}
Expand Down

0 comments on commit a576aa4

Please sign in to comment.