Skip to content

SI-9076 REPL wrap is App #4246

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

Closed
wants to merge 1 commit into from
Closed

Conversation

som-snytt
Copy link
Contributor

Avoid class initialization traps in the object wrapper by
extending App.

The App object is initialized in the usual place, namely
the print method of the eval object, by invoking main.

scala> 42 // show
object $read extends scala.AnyRef {
  def <init>() = {
    super.<init>;
    ()
  };
  object $iw extends App {
    def <init>() = {
      super.<init>;
      ()
    };
    val res0 = 42
  };
}
[[syntax trees at end of                     typer]] // <console>
package $line3 {
  object $read extends scala.AnyRef {
    def <init>(): $line3.$read.type = {
      $read.super.<init>();
      ()
    };
    object $iw extends AnyRef with App {
      def <init>(): type = {
        $iw.super.<init>();
        ()
      };
      private[this] val res0: Int = 42;
      <stable> <accessor> def res0: Int = $iw.this.res0
    }
  }
}

[[syntax trees at end of                     typer]] // <console>
package $line3 {
  object $eval extends scala.AnyRef {
    def <init>(): $line3.$eval.type = {
      $eval.super.<init>();
      ()
    };
    lazy private[this] var $result: Int = _;
    <stable> <accessor> lazy def $result: Int = {
      $eval.this.$result = $line3.$read.$iw.res0;
      $eval.this.$result
    };
    lazy private[this] var $print: String = _;
    <stable> <accessor> lazy def $print: String = {
      $eval.this.$print = {
        $line3.$read.$iw.main(null);
        "res0: Int = ".+(scala.runtime.ScalaRunTime.replStringOf($line3.$read.$iw.res0, 1000))
      };
      $eval.this.$print
    }
  }
}

res0: Int = 42

@scala-jenkins scala-jenkins added this to the 2.11.6 milestone Jan 13, 2015
@som-snytt
Copy link
Contributor Author

Removing the extra wrapper means error message lines are off by one, and messages reporting the fully qualified name are shorter. I won't push an --update-check until someone likes this PR on Facebook.

-<console>:6: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses
+<console>:7: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses

-    import $line44.$read.$iw.BippyBups;
+    import $line44.$read.$iw.$iw.BippyBups;

Are BippyBups those candies they used to hand out at Halloween?

@retronym
Copy link
Member

Looks really promising.

I've performed some cursory testing but haven't managed to break it just yet.

% scala-hash v2.11.4
Welcome to Scala version 2.11.4-20141023-110636-d783face36 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def foo = 42; List(1).par.map(_ => foo)
^C

% qscala
Welcome to Scala version 2.11.5-20150113-032025-54c8d720ba (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> List(1, 2, 3).par.map(x => x + x)
res0: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(2, 4, 6)

scala> println("!!")
!!

scala> :silent
Switched off result printing.

scala> println("!!")
!!

I would suggest adding a setting to restore the old behaviour in case of unforseen problems. I know that Spark and ScalaLab uses the REPL in a slightly custom manner. /cc @dragos @heathermiller

@retronym
Copy link
Member

P.S. Nice use of // show!

@som-snytt
Copy link
Contributor Author

This breaks SpecializedTypes.transformSelect in run/t6434.scala.

matchingSymbolInPrefix says assertion failed: List(method apply$mcI$sp, method apply$mcI$sp).

Manually, it works with // show enabled, so presumably a Symbol init problem. Or, as Amelia Bedelia would say, a Symbol un init problem. (Edit: it fails the first time, but after "abandon crashed session", succeeds the second time. Or wait, it simply alternates fail, success, fail... That sounds like a resident compiler ticket. It starts out failing after :reset.)

scala> def f(x: => Int): Int = x // show
object $read extends scala.AnyRef {
  def <init>() = {
    super.<init>;
    ()
  };
  object $iw extends App {
    def <init>() = {
      super.<init>;
      ()
    };
    def f(x: _root_.scala.<byname>[Int]): Int = x
  }
}
f: (x: => Int)Int

Edit: it also fails pasted raw, so something is broken:

scala> :pa -raw
// Entering paste mode (ctrl-D to finish)

package badby

object X {
  object Y extends App {
    def f(x: => Int): Int = x
  }
}

@som-snytt
Copy link
Contributor Author

Pushed with updated check files, a fix for evaluation by script engine, and a user option to select the template, -YreplWrap:class,object,app. The previous -Yrepl-class-based will set "class". Broken test t6434 still fails.

@som-snytt som-snytt force-pushed the issue/9076 branch 2 times, most recently from dc02a88 to c49dee9 Compare January 15, 2015 04:07
@som-snytt
Copy link
Contributor Author

More check file updates and a test.

@retronym
Copy link
Member

fail - run/t6434.scala [output differs]% scalac t6434.scala

Rebasing on #4249 should fix this one.

@retronym
Copy link
Member

If you rebase, could you give an example of the sort of class initialization trap in the commit comment. The .par.map one in the test case will do the trick.

Avoid class initialization traps in the object wrapper by
extending App.

Previously, the following line would hang in the REPL
because the parallel operation was started from the constructor
of the wrapping object, i.e., from the static initializer of
the class. Class-loading the closure would deadlock on loading
the wrapper, which in turn blocks on completion of the par.map.

```
scala> def i = 42 ; List(1, 2, 3).par.map(x => x + i)
```

Any user code that starts a thread could deadlock, including
innocent experiments with Futures in for comprehensions.

The App object is initialized in the usual place, namely
the print method of the eval object, by invoking main.
(A lazy `compute` accommodates two paths of evaluation:
`print` for REPL, `result` for script engine.)

A compiler option `-YreplWrap` takes values "class", "object"
and "app" (default) to select the wrapper template.

```
scala> 42 // show
object $read extends scala.AnyRef {
  def <init>() = {
    super.<init>;
    ()
  };
  object $iw extends App {
    def <init>() = {
      super.<init>;
      ()
    };
    val res0 = 42
  };
}
[[syntax trees at end of                     typer]] // <console>
package $line3 {
  object $read extends scala.AnyRef {
    def <init>(): $line3.$read.type = {
      $read.super.<init>();
      ()
    };
    object $iw extends AnyRef with App {
      def <init>(): type = {
        $iw.super.<init>();
        ()
      };
      private[this] val res0: Int = 42;
      <stable> <accessor> def res0: Int = $iw.this.res0
    }
  }
}

[[syntax trees at end of                     typer]] // <console>
package $line3 {
  object $eval extends scala.AnyRef {
    def <init>(): $line3.$eval.type = {
      $eval.super.<init>();
      ()
    };
    <stable> <accessor> lazy def compute: Unit = $line3.$read.$iw.main(null);
    lazy private[this] var $result: Int = _;
    <stable> <accessor> lazy def $result: Int = {
      $eval.this.$result = {
        $eval.this.compute;
        $line3.$read.$iw.res0
      };
      $eval.this.$result
    };
    lazy private[this] var $print: String = _;
    <stable> <accessor> lazy def $print: String = {
      $eval.this.$print = {
        $eval.this.compute;
        "res0: Int = ".+(scala.runtime.ScalaRunTime.replStringOf($line3.$read.$iw.res0, 1000))
      };
      $eval.this.$print
    }
  }
}

res0: Int = 42

```
@som-snytt
Copy link
Contributor Author

Rebased. Sorry for the bother, Jenkins, I neglected to amend the commit message, which I've now done. I hope I didn't put you to any trouble, there's a good chap.

@@ -197,7 +197,14 @@ trait ScalaSettings extends AbsScalaSettings
val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal)
val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") withDeprecationMessage(s"Use ${Ymacroexpand.name}:${MacroExpand.None}") withPostSetHook(_ => Ymacroexpand.value = MacroExpand.None)
val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup")
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects")
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects") withPostSetHook (self => if (self) YreplWrap.value = "class")
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 see you don't set replWrap on -Yrepl-class-based:false. Boolean flags used to be so much simpler to reason about.

@som-snytt
Copy link
Contributor Author

Tests pass, but IDE tackled me short of the goal line.

@retronym
Copy link
Member

PLS REBUILD ALL

@scala-jenkins
Copy link

(kitty-note-to-self: ignore 70583165)
🐱 Roger! Rebuilding pr-scala for e017968. 🚨

@adriaanm adriaanm self-assigned this Jan 30, 2015
@adriaanm
Copy link
Contributor

adriaanm commented Feb 2, 2015

/cc @gzm0

val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects")
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects") withPostSetHook (self => if (self) YreplWrap.value = "class")
val YreplWrap = ChoiceSetting(
name = "-YreplWrap",
Copy link
Member

Choose a reason for hiding this comment

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

-Yrepl-wrap.

@retronym
Copy link
Member

retronym commented Feb 5, 2015

I think we should go for the more conservative approach of introducing this as a non-default wrapper in 2.11.6, and after gathering some experience switching to it by default in 2.12.0.

As an example of the sort of thing that people might rely on: I remember once using IMain to compile snippets of code, and I had some logic to map the line numbers in compiler errors to an offset within the source, rather than the overall line within the wrapper. The changing line numbers in the new wrapper might have broken that code.

@retronym
Copy link
Member

retronym commented Feb 5, 2015

Do we expect the generated code to have changed from 2.11.5 to -YreplWrap:object? I seem to see a difference: https://gist.github.com/retronym/af5f055baacef98af176

@som-snytt
Copy link
Contributor Author

Conservative -Yrepl-wrap is the right thing.

The commit removes (presumably extraneous) wrappers; I couldn't find a use case for it. I'd have to refresh my memory if the "force initialization" changes, too.

IIRC, it was a fix at some point to make repl line numbers correspond to the source.

@som-snytt som-snytt closed this Feb 10, 2015
@som-snytt
Copy link
Contributor Author

now #4311

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.

4 participants