Skip to content

Fix #4230: Handle import in the REPL correctly #4915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 3, 2018

Conversation

allanrenucci
Copy link
Contributor

We used to collect imports from the parsed tree and insert them as
parsed trees at the top of the REPL wrapper object. This caused members
shadowing issues.

We now introduce a phase in the REPL compiler that collects imports
after type checking and store them as typed tree. We can then create a
context with its imports set in the correct order and use it to compile
future expressions.

@@ -0,0 +1,30 @@
package dotty.tools.repl
Copy link
Member

Choose a reason for hiding this comment

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

maybe put it in repl.transform ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even if that's the only one?

*/
class ReplCompiler extends Compiler {
override protected def frontendPhases: List[List[Phase]] =
Phases.replace(classOf[FrontEnd], _ => new REPLFrontEnd :: Nil, super.frontendPhases)
Copy link
Member

Choose a reason for hiding this comment

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

I think keeping Phases.replace would be cleaner, it just needs to be passed a list of list of phases as a replacement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would be trickier. I need to replace FrontEnd by ReplFrontend, remove sbt.ExtractDependencies and sbt.ExtractAPI and finally add CollectTopLevelImports which needs to run after ReplFrontEnd but in its own group

Copy link
Member

Choose a reason for hiding this comment

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

Ah indeed, not worth it then

@allanrenucci
Copy link
Contributor Author

I have a pickling issue:

class dotty.tools.dotc.reporting.diagnostic.messages$Error at ?: pickling difference for class ReplCompiler in dotty/compiler/src/dotty/tools/repl/ReplCompiler.scala
> diff before-pickling.txt after-pickling.txt
2857c2857,2861
<                           :Either[dotty.tools.repl.results.Errors, dotty.tools.dotc.ast.tpd.ValDef]>@<9296..9440>
---
>                           :
>                             Either[dotty.tools.repl.results.Errors, 
>                               dotty.tools.dotc.ast.Trees.ValDef[dotty.tools.dotc.core.Types.Type]
>                             ]
>                           >@<9296..9440>
2859c2863,2867
<                       :Either[dotty.tools.repl.results.Errors, dotty.tools.dotc.ast.tpd.ValDef]>@<9181..9440>
---
>                       :
>                         Either[dotty.tools.repl.results.Errors, 
>                           dotty.tools.dotc.ast.Trees.ValDef[dotty.tools.dotc.core.Types.Type]
>                         ]
>                       >@<9181..9440>

I am not sure how to debug that. cc/ @smarter @nicolasstucki

@nicolasstucki
Copy link
Contributor

I'll have a look tomorrow

@nicolasstucki
Copy link
Contributor

I think I found the place where it happens in 3402a29 (5cbb512 needs to be reverted, sorry). To debug it you should try writing 3402a29#diff-c7dacf9ef97cabd025fede32386ffdecR269 in some other smaller class, then you should be able to reproduce it with dotc with the TestConfiguration.picklingOptions flags.

@nicolasstucki
Copy link
Contributor

@allanrenucci in a01be64 I added a test that reproduces the issue. You can minimize it from that test file.

@allanrenucci
Copy link
Contributor Author

@nicolasstucki Thanks!

@allanrenucci
Copy link
Contributor Author

allanrenucci commented Aug 22, 2018

With this new scheme I still cannot handle:

scala> object A { def f = 1 }                                                                                                                                                                                   
// defined object A

scala> import A._; def f = 2
def f: Int

scala> f                                                                                                                                                                                                        
val res0: Int = 1 // should probably be 2

The scalac REPL handles it properly, but they use a complex object nesting scheme that we don't want to replicate

@allanrenucci allanrenucci requested a review from smarter August 22, 2018 15:02
@@ -329,7 +329,7 @@ object Interactive {

def contextOfPath(path: List[Tree])(implicit ctx: Context): Context = path match {
case Nil | _ :: Nil =>
ctx.run.runContext.fresh.setCompilationUnit(ctx.compilationUnit)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@odersky Was this intentional? If so, what's the rational for using the run context?

Copy link
Member

Choose a reason for hiding this comment

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

Pretty sure that's intentional. contextOfPath constructs a context with the correct scoping information. If you start from a random Context then you'll have too much stuff in the scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could patch the rootContext of the new run as we used to do:

class ReplCompiler extends Compiler {
  def newRun(initCtx: Context, state: State) = new Run(this, initCtx) {
    override protected[this] def rootContext(implicit ctx: Context) =
      addMagicImports(super.rootContext)
   
    private def addMagicImports(initCtx: Context): Context = ...
  }
}

This way I wouldn't need to modify this line

class CollectTopLevelImports extends Phase {
import tpd._

def phaseName = "collecttoplevelimports"
Copy link
Member

Choose a reason for hiding this comment

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

we usually use camelCase.

* custom subclass of `GenBCode`. The custom `GenBCode`, `REPLGenBCode`, works
* in conjunction with a specialized class loader in order to load virtual
* classfiles.
* Specifically it replaces the front end with `REPLFrontEnd`.
*/
Copy link
Member

Choose a reason for hiding this comment

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

I would just delete this whole comment, it doesn't really say anything interesting. (it could be replaced by an actual description of what REPLCompiler does though :))

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Actually #4915 (comment) needs to be addressed.

@smarter
Copy link
Member

smarter commented Aug 24, 2018

With this new scheme I still cannot handle:

Can you open an issue for that?

@smarter smarter assigned allanrenucci and unassigned smarter Aug 24, 2018
@allanrenucci allanrenucci force-pushed the fix-4230 branch 2 times, most recently from e40d390 to 2548813 Compare August 28, 2018 09:15
@allanrenucci
Copy link
Contributor Author

@smarter comments addressed

@allanrenucci allanrenucci removed their assignment Aug 28, 2018
@allanrenucci
Copy link
Contributor Author

Can you open an issue for that?

Will do once this is merged

We used to collect user defined imports from the parsed tree and
insert them as untyped trees at the top of the REPL wrapper object.
This caused members shadowing issues.

We now introduce a phase in the REPL compiler that collects imports
after type checking and store them as typed tree. We can then create a
context with its imports set in the correct order and use it to compile
future expressions.

This also fixes scala#4978 by having user defined import in the run context.
Auto-completions somehow ignored them when they were part of the
untyped tree.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants