|
| 1 | +package dotty.tools.repl |
| 2 | + |
| 3 | +import dotty.tools.backend.jvm.GenBCode |
| 4 | +import dotty.tools.dotc.ast.Trees._ |
| 5 | +import dotty.tools.dotc.ast.{tpd, untpd} |
| 6 | +import dotty.tools.dotc.ast.tpd.TreeOps |
| 7 | +import dotty.tools.dotc.core.Comments.CommentsContext |
| 8 | +import dotty.tools.dotc.core.Contexts._ |
| 9 | +import dotty.tools.dotc.core.Decorators._ |
| 10 | +import dotty.tools.dotc.core.Flags._ |
| 11 | +import dotty.tools.dotc.core.Names._ |
| 12 | +import dotty.tools.dotc.core.Phases |
| 13 | +import dotty.tools.dotc.core.Phases.Phase |
| 14 | +import dotty.tools.dotc.core.StdNames._ |
| 15 | +import dotty.tools.dotc.core.Symbols._ |
| 16 | +import dotty.tools.dotc.reporting.diagnostic.messages |
| 17 | +import dotty.tools.dotc.transform.PostTyper |
| 18 | +import dotty.tools.dotc.typer.{FrontEnd, ImportInfo} |
| 19 | +import dotty.tools.dotc.util.Positions._ |
| 20 | +import dotty.tools.dotc.util.SourceFile |
| 21 | +import dotty.tools.dotc.{CompilationUnit, Compiler, Run} |
| 22 | +import dotty.tools.io._ |
| 23 | +import dotty.tools.repl.results._ |
| 24 | + |
| 25 | +import scala.collection.mutable |
| 26 | + |
| 27 | +/** This subclass of `Compiler` replaces the appropriate phases in order to |
| 28 | + * facilitate the REPL |
| 29 | + * |
| 30 | + * Specifically it replaces the front end with `REPLFrontEnd`. |
| 31 | + */ |
| 32 | +class ReplCompiler2 extends Compiler { |
| 33 | + |
| 34 | + override protected def frontendPhases: List[List[Phase]] = List( |
| 35 | + List(new REPLFrontEnd), |
| 36 | + List(new CollectTopLevelImports), |
| 37 | + List(new PostTyper) |
| 38 | + ) |
| 39 | + |
| 40 | + def newRunContext(initCtx: Context, state: State): Context = { |
| 41 | + def addUserDefinedImport(imp: tpd.Import)(implicit ctx: Context) = |
| 42 | + ctx.importContext(imp, imp.symbol) |
| 43 | + |
| 44 | + def importModule(path: TermName)(implicit ctx: Context) = { |
| 45 | + val importInfo = ImportInfo.rootImport(() => |
| 46 | + ctx.requiredModuleRef(path)) |
| 47 | + ctx.fresh.setNewScope.setImportInfo(importInfo) |
| 48 | + } |
| 49 | + |
| 50 | + val run = newRun(initCtx.fresh.setReporter(newStoreReporter)) |
| 51 | + (1 to state.objectIndex).foldLeft(run.runContext) { (ctx, i) => |
| 52 | + // we first import the wrapper object i |
| 53 | + val path = nme.EMPTY_PACKAGE ++ "." ++ objectNames(i) |
| 54 | + val ctx0 = importModule(path)(ctx) |
| 55 | + // then its user defined imports |
| 56 | + val imports = state.imports.getOrElse(i, Nil) |
| 57 | + if (imports.isEmpty) ctx0 |
| 58 | + else imports.foldLeft(ctx0.fresh.setNewScope)((ctx, imp) => |
| 59 | + addUserDefinedImport(imp)(ctx)) |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + private[this] val objectNames = mutable.Map.empty[Int, TermName] |
| 64 | + private def objectName(state: State) = |
| 65 | + objectNames.getOrElseUpdate(state.objectIndex, |
| 66 | + (str.REPL_SESSION_LINE + state.objectIndex).toTermName) |
| 67 | + |
| 68 | + private case class Definitions(stats: List[untpd.Tree], state: State) |
| 69 | + |
| 70 | + private def definitions(trees: List[untpd.Tree], state: State): Definitions = { |
| 71 | + import untpd._ |
| 72 | + |
| 73 | + implicit val ctx: Context = state.context |
| 74 | + |
| 75 | + var valIdx = state.valIndex |
| 76 | + |
| 77 | + val defs = trees.flatMap { |
| 78 | + case expr @ Assign(id: Ident, _) => |
| 79 | + // special case simple reassignment (e.g. x = 3) |
| 80 | + // in order to print the new value in the REPL |
| 81 | + val assignName = (id.name ++ str.REPL_ASSIGN_SUFFIX).toTermName |
| 82 | + val assign = ValDef(assignName, TypeTree(), id).withPos(expr.pos) |
| 83 | + List(expr, assign) |
| 84 | + case expr if expr.isTerm => |
| 85 | + val resName = (str.REPL_RES_PREFIX + valIdx).toTermName |
| 86 | + valIdx += 1 |
| 87 | + val vd = ValDef(resName, TypeTree(), expr).withPos(expr.pos) |
| 88 | + vd :: Nil |
| 89 | + case other => |
| 90 | + other :: Nil |
| 91 | + } |
| 92 | + |
| 93 | + Definitions( |
| 94 | + defs, |
| 95 | + state.copy( |
| 96 | + objectIndex = state.objectIndex + (if (defs.isEmpty) 0 else 1), |
| 97 | + valIndex = valIdx |
| 98 | + ) |
| 99 | + ) |
| 100 | + } |
| 101 | + |
| 102 | + /** Wrap trees in an object and add imports from the previous compilations |
| 103 | + * |
| 104 | + * The resulting structure is something like: |
| 105 | + * |
| 106 | + * ``` |
| 107 | + * package <none> { |
| 108 | + * object rs$line$nextId { |
| 109 | + * import rs$line${i <- 0 until nextId}._ |
| 110 | + * |
| 111 | + * <trees> |
| 112 | + * } |
| 113 | + * } |
| 114 | + * ``` |
| 115 | + */ |
| 116 | + private def wrapped(defs: Definitions): untpd.PackageDef = { |
| 117 | + import untpd._ |
| 118 | + |
| 119 | + assert(defs.stats.nonEmpty) |
| 120 | + |
| 121 | + implicit val ctx: Context = defs.state.context |
| 122 | + |
| 123 | + val tmpl = Template(emptyConstructor, Nil, EmptyValDef, defs.stats) |
| 124 | + val module = ModuleDef(objectName(defs.state), tmpl) |
| 125 | + .withPos(Position(0, defs.stats.last.pos.end)) |
| 126 | + |
| 127 | + PackageDef(Ident(nme.EMPTY_PACKAGE), List(module)) |
| 128 | + } |
| 129 | + |
| 130 | + private def createUnit(defs: Definitions, sourceCode: String): CompilationUnit = { |
| 131 | + val unit = new CompilationUnit(new SourceFile(objectName(defs.state).toString, sourceCode)) |
| 132 | + unit.untpdTree = wrapped(defs) |
| 133 | + unit |
| 134 | + } |
| 135 | + |
| 136 | + private def runCompilationUnit(unit: CompilationUnit, state: State): Result[(CompilationUnit, State)] = { |
| 137 | + val ctx = state.context |
| 138 | + ctx.run.compileUnits(unit :: Nil, ctx) |
| 139 | + |
| 140 | + if (!ctx.reporter.hasErrors) (unit, state).result |
| 141 | + else ctx.reporter.removeBufferedMessages(ctx).errors |
| 142 | + } |
| 143 | + |
| 144 | + final def compile(parsed: Parsed)(implicit state: State): Result[(CompilationUnit, State)] = { |
| 145 | + val defs = definitions(parsed.trees, state) |
| 146 | + val unit = createUnit(defs, parsed.sourceCode) |
| 147 | + runCompilationUnit(unit, defs.state) |
| 148 | + } |
| 149 | + |
| 150 | + final def typeOf(expr: String)(implicit state: State): Result[String] = |
| 151 | + typeCheck(expr).map { tree => |
| 152 | + implicit val ctx = state.context |
| 153 | + tree.rhs match { |
| 154 | + case Block(xs, _) => xs.last.tpe.widen.show |
| 155 | + case _ => |
| 156 | + """Couldn't compute the type of your expression, so sorry :( |
| 157 | + | |
| 158 | + |Please report this to my masters at github.com/lampepfl/dotty |
| 159 | + """.stripMargin |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + def docOf(expr: String)(implicit state: State): Result[String] = { |
| 164 | + implicit val ctx: Context = state.context |
| 165 | + |
| 166 | + /** Extract the "selected" symbol from `tree`. |
| 167 | + * |
| 168 | + * Because the REPL typechecks an expression, special syntax is needed to get the documentation |
| 169 | + * of certain symbols: |
| 170 | + * |
| 171 | + * - To select the documentation of classes, the user needs to pass a call to the class' constructor |
| 172 | + * (e.g. `new Foo` to select `class Foo`) |
| 173 | + * - When methods are overloaded, the user needs to enter a lambda to specify which functions he wants |
| 174 | + * (e.g. `foo(_: Int)` to select `def foo(x: Int)` instead of `def foo(x: String)` |
| 175 | + * |
| 176 | + * This function returns the right symbol for the received expression, and all the symbols that are |
| 177 | + * overridden. |
| 178 | + */ |
| 179 | + def extractSymbols(tree: tpd.Tree): Iterator[Symbol] = { |
| 180 | + val sym = tree match { |
| 181 | + case tree if tree.isInstantiation => tree.symbol.owner |
| 182 | + case tpd.closureDef(defdef) => defdef.rhs.symbol |
| 183 | + case _ => tree.symbol |
| 184 | + } |
| 185 | + Iterator(sym) ++ sym.allOverriddenSymbols |
| 186 | + } |
| 187 | + |
| 188 | + typeCheck(expr).map { |
| 189 | + case ValDef(_, _, Block(stats, _)) if stats.nonEmpty => |
| 190 | + val stat = stats.last.asInstanceOf[tpd.Tree] |
| 191 | + if (stat.tpe.isError) stat.tpe.show |
| 192 | + else { |
| 193 | + val docCtx = ctx.docCtx.get |
| 194 | + val symbols = extractSymbols(stat) |
| 195 | + val doc = symbols.collectFirst { |
| 196 | + case sym if docCtx.docstrings.contains(sym) => |
| 197 | + docCtx.docstrings(sym).raw |
| 198 | + } |
| 199 | + doc.getOrElse(s"// No doc for `${expr}`") |
| 200 | + } |
| 201 | + |
| 202 | + case _ => |
| 203 | + """Couldn't display the documentation for your expression, so sorry :( |
| 204 | + | |
| 205 | + |Please report this to my masters at github.com/lampepfl/dotty |
| 206 | + """.stripMargin |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + final def typeCheck(expr: String, errorsAllowed: Boolean = false)(implicit state: State): Result[tpd.ValDef] = { |
| 211 | + |
| 212 | + def wrapped(expr: String, sourceFile: SourceFile, state: State)(implicit ctx: Context): Result[untpd.PackageDef] = { |
| 213 | + def wrap(trees: List[untpd.Tree]): untpd.PackageDef = { |
| 214 | + import untpd._ |
| 215 | + |
| 216 | + val valdef = ValDef("expr".toTermName, TypeTree(), Block(trees, unitLiteral)) |
| 217 | + val tmpl = Template(emptyConstructor, Nil, EmptyValDef, List(valdef)) |
| 218 | + val wrapper = TypeDef("$wrapper".toTypeName, tmpl) |
| 219 | + .withMods(Modifiers(Final)) |
| 220 | + .withPos(Position(0, expr.length)) |
| 221 | + PackageDef(Ident(nme.EMPTY_PACKAGE), List(wrapper)) |
| 222 | + } |
| 223 | + |
| 224 | + ParseResult(expr) match { |
| 225 | + case Parsed(_, trees) => |
| 226 | + wrap(trees).result |
| 227 | + case SyntaxErrors(_, reported, trees) => |
| 228 | + if (errorsAllowed) wrap(trees).result |
| 229 | + else reported.errors |
| 230 | + case _ => List( |
| 231 | + new messages.Error( |
| 232 | + s"Couldn't parse '$expr' to valid scala", |
| 233 | + sourceFile.atPos(Position(0, expr.length)) |
| 234 | + ) |
| 235 | + ).errors |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + def unwrapped(tree: tpd.Tree, sourceFile: SourceFile)(implicit ctx: Context): Result[tpd.ValDef] = { |
| 240 | + def error: Result[tpd.ValDef] = |
| 241 | + List(new messages.Error(s"Invalid scala expression", |
| 242 | + sourceFile.atPos(Position(0, sourceFile.content.length)))).errors |
| 243 | + |
| 244 | + import tpd._ |
| 245 | + tree match { |
| 246 | + case PackageDef(_, List(TypeDef(_, tmpl: Template))) => |
| 247 | + tmpl.body |
| 248 | + .collectFirst { case dd: ValDef if dd.name.show == "expr" => dd.result } |
| 249 | + .getOrElse(error) |
| 250 | + case _ => |
| 251 | + error |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + |
| 256 | + val src = new SourceFile("<typecheck>", expr) |
| 257 | + implicit val ctx: Context = state.context.fresh |
| 258 | + .setReporter(newStoreReporter) |
| 259 | + .setSetting(state.context.settings.YstopAfter, List("frontend")) |
| 260 | + |
| 261 | + wrapped(expr, src, state).flatMap { pkg => |
| 262 | + val unit = new CompilationUnit(src) |
| 263 | + unit.untpdTree = pkg |
| 264 | + ctx.run.compileUnits(unit :: Nil, ctx) |
| 265 | + |
| 266 | + if (errorsAllowed || !ctx.reporter.hasErrors) |
| 267 | + unwrapped(unit.tpdTree, src) |
| 268 | + else |
| 269 | + ctx.reporter.removeBufferedMessages.errors |
| 270 | + } |
| 271 | + } |
| 272 | +} |
0 commit comments