-
Notifications
You must be signed in to change notification settings - Fork 1.1k
REPL: chained extension methods on opaque types can only be called once #10044
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
Comments
@anatoliykmetyuk Not sure who to assign this to, are you the most knowledgeable person on REPL at the moment? |
The type seems to change subtly.
Also
|
Seems to work in 3.0-M3 |
Still broken on M3 and also current master
also per previous comment
|
This seems to be fixed for IArray since #11354 , but it can be reproduced by compiling the the old definition under a new name and adding to the classpath in the |
@bishabosha could you include the code you needed to reproduce it? |
@nicolasstucki I have updated the original post |
Is this the same issue or something new? ➜ cs launch scala3-repl
scala> object Foo:
| opaque type Bar = Int
| object Bar:
| extension (b: Bar) def flip: Bar = -b
| def apply(x: Int): Bar = x
|
// defined object Foo
scala> val a = Foo.Bar(42)
val a: Foo.Bar = 42
scala> val b = a.flip
val b: Foo.Bar = -42
scala> val c = b.flip
val c: Foo.Bar = 42
scala> val d = c.flip
1 |val d = c.flip
| ^^^^^^
|value flip is not a member of Foo.Bar, but could be made available as an extension method.
|
|The following import might fix the problem:
|
| import Foo.Bar.flip
|
scala> |
The original example now has a different problem on master, the second invocation of the extension method works but the result type is no longer opaque: Welcome to Scala 3.1.0-RC1-bin-SNAPSHOT-git-7d1cddb (1.8.0_292, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
scala> import example.IArr
scala> IArr(1,2,3).reverse.sorted
val res0: example.opaques.IArr[Int] = Array(1, 2, 3)
scala> IArr(1,2,3).reverse.sorted
val res1: Array[? <: Int] = Array(1, 2, 3) This behavior seems to have started with #13260 Interestingly, both the original example and the one from @mpilquist seem to work correctly if #11300 is reverted, but I suspect that's just papering over the real issue. |
Using a customized REPL where scala> object Foo:
| opaque type Bar = Int
| object Bar:
| extension (b: Bar) def flip: Bar = -b
| def apply(x: Int): Bar = x
|
// defined object Foo
scala> val a = Foo.Bar(42)
val a: Foo.Bar = 42
scala> val b0 = a.flip
val b0: Foo.Bar = -42
scala> val b1 = a.flip
val b1: Foo.Bar = -42
scala> :dealiased b0
TypeRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix,module class repl$)),object rs$line$1),object Foo),type Bar)
scala> :dealiased b1
TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)
scala> :rawtype b0
TypeRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix,module class repl$)),object rs$line$1),object Foo),type Bar)
scala> :rawtype b1
TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl$)),module class rs$line$1$)),module class Foo$)),type Bar)
scala> val c = b0.flip
val c: Foo.Bar = 42
scala> val d = c.flip
-- Error:
1 |val d = c.flip
| ^^^^^^
| value flip is not a member of Foo.Bar, but could be made available as an extension method.
|
| The following import might fix the problem:
|
| import Foo.Bar.flip
|
scala> :dealiased c
TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)
scala> :rawtype c
TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl$)),module class rs$line$1$)),module class Foo$)),type Bar) |
fwiw, #13260 doesn't affect Michael's case either way. my intuition is that Michael's case should be investigated first, and then we'd circle back to the original report a plausible next investigation step here would be to analyze why, in Tom's last comment, |
Some side notes that may or may not prove helpful to understanding this... First, note that only one
Second, note that the problem goes away if you
that's because when typer erroneously infers (But it doesn't matter I guess, because we want to fix the wrongly inferred type.) |
One more aside: even though the REPL doesn't show it without Tom's patches, you can detect that the problem is an incorrectly inferred
It makes me wonder if it's actually a bug in the type printer that it prints |
It seems peculiar to me that the compiler accepts this:
if |
ha, interesting as Dale and I were discussing, perhaps we're being too harsh to classify we'll keep digging |
as Dale notes, this seems like a resident compiler bug rather than a "REPL bug" per se, it's just that the REPL is the usual way a resident compiler gets involved |
We suspect this has to do with stale denotations. From Tom's script we found out that not only do the types of In terms of process we eventually found a reasonable debugging setup in IntelliJ that allowed us to step through the logic in Typer or in objects, which was sometimes faster than println debugging. Here's the branch where Seth and I almost lost our minds trying to force IntelliJ to set a break point: https://github.com/lampepfl/dotty/compare/master...dwijnand:opaque-extensions-on-life-support?expand=1 |
haha yeah if some of the diffs look like bizarre flailing that's why, in general the debugger works well, but there is a specific pain point in the Scala 3 support: sometimes it simply won't let you set a breakpoint on a certain line, and we couldn't tell what the conditions are for that, so then we'd semi-randomly rearrange the code until it would let us set one where we needed it also conditional breakpoints didn't seem to be reliable but we just substituted "poor man's conditional breakpoints" where you add |
a random tidbit is that once we've enabled tracing in some part of the compiler |
This comment was marked as outdated.
This comment was marked as outdated.
Let me attempt to explain what's happening here. I'm going to focus on the observation above that scala> object Foo: // (Run 1)
opaque type Bar = Int
object Bar:
extension (b: Bar) def flip: Bar = -b
def apply(x: Int): Bar = x
scala> val a = Foo.Bar(42) // (Run 2)
scala> val b0 = a.flip // (Run 3)
scala> val b1 = a.flip // (Run 4)
scala> val c = b1.flip // (Run 5) Now let's look at what happens during the compilation runs, one for each "line" entered into the REPL. In Run 1 A SymDenotation is created for the symbol
In Run 2 A TermRef is created for
In Run 3 During phase typer: Implicit search is performed. The
During phase genBCode: At some point
The problem is with the subsequent call to which calls The gotcha is that we are calling this from the backend, but after erasure, So in computing the asSeenFrom,
so the result of our asSeenFrom ends up being the original Then the implementation of
Based on the validity period of this SymDenotation, it will be brought forward into the next run, and used during typer without being recomputed! In Run 4: During phase typer: The In Run 5: During phase typer: The mis-typing of |
That seems to be the real issue, the validity period shouldn't include phases where the denotation isn't actually valid, otherwise problems like this could pop up in any random place |
or perhaps that's the real culprit, I always found our usage of setDenot suspicious and this seems to confirm that we can't just assume that a TermRef with a specific denotation can stand in for any TermRef with the same prefix and designator, even if the denotation is still considered valid. |
Yes, I think we need to handle travelling back in time wrt phases specially. A denotation established at a later phase cannot be re-used for the same NamedType in previous phases, even if the denotation is still valid. |
This addresses the specific problem raised by scala#10044: A denotation of a NamedType goes from a UniqueRefDenotation to a SymDenotation after erasure and is then not reset to the original since the SymDenotation is valid at all phases. We remember in this case in the NamedType the first phase in which the SymDenotaton is valid and force a recompute in earlier phases. Fixes scala#10044
This addresses the specific problem raised by scala#10044: A denotation of a NamedType goes from a UniqueRefDenotation to a SymDenotation after erasure and is then not reset to the original since the SymDenotation is valid at all phases. We remember in this case in the NamedType the first phase in which the SymDenotaton is valid and force a recompute in earlier phases. Fixes scala#10044
@griggt Thank you for such a great diagnosis! |
This addresses the specific problem raised by scala#10044: A denotation of a NamedType goes from a UniqueRefDenotation to a SymDenotation after erasure and is then not reset to the original since the SymDenotation is valid at all phases. We remember in this case in the NamedType the first phase in which the SymDenotaton is valid and force a recompute in earlier phases. Fixes scala#10044
This addresses the specific problem raised by scala#10044: A denotation of a NamedType goes from a UniqueRefDenotation to a SymDenotation after erasure and is then not reset to the original since the SymDenotation is valid at all phases. We remember in this case in the NamedType the first phase in which the SymDenotaton is valid and force a recompute in earlier phases. Fixes scala#10044
Minimized code
Only in the REPL, it seems like calling a chain of extension methods has a strange side effect that prevents it from happening again.
First, compile this source:
Then run the
repl
task with IArr on the classpathExpectation
this is ok when they are called one after the other in a source file
The text was updated successfully, but these errors were encountered: