Skip to content

Commit a01be64

Browse files
committed
Reproduce pickling failure in compilation test
Can be reproduced with `sbt "dotty-bootstrapped/testCompilation ReplCompiler2"`
1 parent d08a9f4 commit a01be64

File tree

3 files changed

+275
-1
lines changed

3 files changed

+275
-1
lines changed

compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting {
8282
@Test def runWithCompiler: Unit = {
8383
implicit val testGroup: TestGroup = TestGroup("runWithCompiler")
8484
compileFilesInDir("tests/run-with-compiler", defaultRunWithCompilerOptions) +
85+
compileFile("tests/run-with-compiler-custom-args/ReplCompiler2.scala", defaultRunWithCompilerOptions and (picklingFlags: _*)) +
8586
compileFile("tests/run-with-compiler-custom-args/staged-streams_1.scala", defaultRunWithCompilerOptions without "-Yno-deep-subtypes")
8687
}.checkRuns()
8788

compiler/test/dotty/tools/vulpix/TestConfiguration.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,13 @@ object TestConfiguration {
5353
val defaultRunWithCompilerOptions = defaultOptions.withRunClasspath(Jars.dottyRunWithCompiler.mkString(":"))
5454
val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes"
5555
val allowDoubleBindings = defaultOptions without "-Yno-double-bindings"
56-
val picklingOptions = defaultOptions and (
56+
val picklingFlags = Array(
5757
"-Xprint-types",
5858
"-Ytest-pickler",
5959
"-Yprint-pos",
6060
"-Yprint-pos-syms"
6161
)
62+
val picklingOptions = defaultOptions and (picklingFlags: _*)
6263
val scala2Mode = defaultOptions and "-language:Scala2"
6364
val explicitUTF8 = defaultOptions and ("-encoding", "UTF8")
6465
val explicitUTF16 = defaultOptions and ("-encoding", "UTF16")
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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

Comments
 (0)