Skip to content
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

Regression in return within by-name param within lazy val #8245

Closed
scabug opened this issue Feb 6, 2014 · 6 comments
Closed

Regression in return within by-name param within lazy val #8245

scabug opened this issue Feb 6, 2014 · 6 comments
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Feb 6, 2014

object A extends App {
  def foo(o: Option[Int]): Int = {
    lazy val i: Int = o.getOrElse(return -1)
    i + 1
  }
       
  println(foo(None))
}

From v2.10.0..v2.10.1~232, this correctly prints -1

After 4c86dbbc492 / #6358, it crashes the compiler:

TypeRef(TypeSymbol(class IntRef extends Serializable))

uncaught exception during compilation: java.lang.IllegalArgumentException
error: java.lang.IllegalArgumentException: Could not find proxy for val nonLocalReturnKey1: Object in List(value nonLocalReturnKey1, value i$lzycompute$1, method foo, object A, package <empty>, package <root>) (currentOwner= method apply )
	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.scala$tools$nsc$transform$LambdaLift$LambdaLifter$$searchIn$1(LambdaLift.scala:303)

More alarmingly, it has been reported that in the original code from which this was extracted, this pattern of code doesn't always crash the compiler, but can instead generate code in which the return only returns from the by-name expression, rather than the enclosing def. I haven't been able to reproduce this.

@scabug
Copy link
Author

scabug commented Feb 6, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8245?orig=1
Reporter: @retronym
Affected Versions: 2.10.1
See #6358

@scabug
Copy link
Author

scabug commented Feb 6, 2014

@retronym said:

return in lazy vals started to be a problem somewhere between 2.10.0.RC4 and 2.10.3:
https://github.com/JetBrains/intellij-scala/commit/f1bf7aec695c750194ff922733bf773f6ad9f6c8
This change was necessary to migrate to 2.10.3. See for example change in ScImplicitlyConvertible. We got weird compiler errors and after some time we realized what's going on and was able to fix it.

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@retronym said:
Reports of incorrect code generation turned out to be a misunderstanding. The crash is the only problem.

@scabug
Copy link
Author

scabug commented Feb 7, 2014

@retronym said:
The tree that reaches the LazyVal phase:

<stable> <accessor> lazy def i#15024(): Int#1750 = {
  <synthetic> val nonLocalReturnKey1#24371: Object#149 = new Object#149();
  try {
    i$lzy#15025 = unbox#14934(o#14716.getOrElse#14953({
      @SerialVersionUID#2329(0) final <synthetic> class $anonfun#24363 extends scala#21.runtime#2648.AbstractFunction0#3471 with Serializable#2326 {
        def <init>#24367(): <$anon: Function0#934> = {
          $anonfun#24363.super.<init>#24362();
          ()
        };
        final def apply#24364(): Nothing#3010 = throw new scala#21.runtime#2648.NonLocalReturnControl$mcI$sp#26283(nonLocalReturnKey1#24371, -1);
        final <bridge> <artifact> def apply#26857(): Object#149 = $anonfun#24363.this.apply#24364()
      };
      (new <$anon: Function0#934>(): Function0#934)
    }));
    i$lzy#15025
  } catch {
    case (ex#24759 @ (_: scala#21.runtime#2648.NonLocalReturnControl#3939)) => if (ex#24759.key#24372().eq#5777(nonLocalReturnKey1#24371))
      ex#24759.value$mcI$sp#26186()
    else
      throw ex#24759
  }
}

What it expects:

i$lzy#15025 = unbox#14934(o#14716.getOrElse#14953({
      @SerialVersionUID#2329(0) final <synthetic> class $anonfun#24363 extends scala#21.runtime#2648.AbstractFunction0#3471 with Serializable#2326 {
        def <init>#24367(): <$anon: Function0#934> = {
          $anonfun#24363.super.<init>#24362();
          ()
        };
        final def apply#24364(): Nothing#3010 = throw new scala#21.runtime#2648.NonLocalReturnControl$mcI$sp#26283(nonLocalReturnKey1#24371, -1);
        final <bridge> <artifact> def apply#26857(): Object#149 = $anonfun#24363.this.apply#24364()
      };
      (new <$anon: Function0#934>(): Function0#934)
    }));
    i$lzy#15025

The tree that it finds happens to have the same shape (a block with one statement and an expression) as what it expects (a statement to initialize the lazy val, and an expression to return it)

debuglog(s"create complete lazy def in $methOrClass for $lazyVal")
      val (block, res) = tree match {
        case Block(List(assignment), res) if !lazyUnit(lazyVal) =>
          (mkBlock(assignment),  res)
        case rhs                          =>
          (mkBlock(rhs),         UNIT)
      }

The upshot is that the definition of nonLocalReturnKey1 ends up hidden inside the synchronized call:

<stable> private def i$lzycompute#27983(): Int#1750 = {
  {
    A#7598.this.synchronized#5786(if (bitmap$0#27975.&#27981(1).==#27982(0))
      {
        <synthetic> val nonLocalReturnKey1#24371: Object#149 = new Object#149();
        bitmap$0#27975 = bitmap$0#27975.|#27980(1);
        ()
      });
    ()
  };
  try {
    i$lzy#15025 = unbox#14934(o#14716.getOrElse#14953({
      @SerialVersionUID#2329(0) final <synthetic> class $anonfun#24363 extends scala#21.runtime#2648.AbstractFunction0#3471 with Serializable#2326 {
        def <init>#24367(): <$anon: Function0#934> = {
          $anonfun#24363.super.<init>#24362();
          ()
        };
        final def apply#24364(): Nothing#3010 = throw new scala#21.runtime#2648.NonLocalReturnControl$mcI$sp#26283(nonLocalReturnKey1#24371, -1);
        final <bridge> <artifact> def apply#26857(): Object#149 = $anonfun#24363.this.apply#24364()
      };
      (new <$anon: Function0#934>(): Function0#934)
    }));
    i$lzy#15025
  } catch {
    case (ex#24759 @ (_: scala#21.runtime#2648.NonLocalReturnControl#3939)) => if (ex#24759.key#24372().eq#5777(nonLocalReturnKey1#24371))
      ex#24759.value$mcI$sp#26186()
    else
      throw ex#24759
  }
}

That fact shows up later on when the function for the by-name argument fails to capture it.

@scabug
Copy link
Author

scabug commented Feb 8, 2014

@scabug
Copy link
Author

scabug commented Feb 8, 2014

@retronym said (edited on Feb 8, 2014 10:00:27 AM UTC):
scala/scala#3488

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

No branches or pull requests

2 participants